Skip to content

Commit

Permalink
Add Android Support (#36)
Browse files Browse the repository at this point in the history
* Add initial android support

* Add support for fitting to bounds

* Preliminary view support

* You can now remove the views with no consequences

* Add changeset

* Fix a bug that made it to where the map would just not show
  • Loading branch information
balloman authored Dec 8, 2023
1 parent 116909c commit 7afa151
Show file tree
Hide file tree
Showing 21 changed files with 744 additions and 203 deletions.
12 changes: 12 additions & 0 deletions .changeset/unlucky-tigers-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@balloman/expo-google-maps": minor
---

Added android support!

Android should have all the support iOS has which includes
- Map Display
- Android has a different method of supplying the api key, so now it will need to be provided with an expo plugin
- Markers
- Support for custom views is here as well, but it might be a little buggy on the android side, so let me know if you encounter any issues
- Polygons
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ module.exports = {
{
files: ["*.ts", "*.tsx", "*.d.ts"],
parserOptions: {
project: ["./tsconfig.json", "./example/tsconfig.json"],
project: [
"./tsconfig.json",
"./example/tsconfig.json",
"./plugin/tsconfig.json",
],
},
},
],
Expand Down
121 changes: 63 additions & 58 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,84 +6,89 @@ group = 'expo.modules.googlemaps'
version = '0.1.0'

buildscript {
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
if (expoModulesCorePlugin.exists()) {
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
}
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
if (expoModulesCorePlugin.exists()) {
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
}

// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

// Ensures backward compatibility
ext.getKotlinVersion = {
if (ext.has("kotlinVersion")) {
ext.kotlinVersion()
} else {
ext.safeExtGet("kotlinVersion", "1.8.10")
// Ensures backward compatibility
ext.getKotlinVersion = {
if (ext.has("kotlinVersion")) {
ext.kotlinVersion()
} else {
ext.safeExtGet("kotlinVersion", "1.8.10")
}
}
}

repositories {
mavenCentral()
}
repositories {
mavenCentral()
}

dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
}
}

afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
}
publishing {
publications {
release(MavenPublication) {
from components.release
}
}
repositories {
maven {
url = mavenLocal().url
}
}
}
repositories {
maven {
url = mavenLocal().url
}
}
}
}

android {
compileSdkVersion safeExtGet("compileSdkVersion", 33)
compileSdkVersion safeExtGet("compileSdkVersion", 33)
compileSdk(33)

compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.majorVersion
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.majorVersion
}

namespace "expo.modules.googlemaps"
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 33)
versionCode 1
versionName "0.1.0"
}
lintOptions {
abortOnError false
}
publishing {
singleVariant("release") {
withSourcesJar()
namespace "expo.modules.googlemaps"
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 33)
versionCode 1
versionName "0.1.0"
}
lintOptions {
abortOnError false
}
publishing {
singleVariant("release") {
withSourcesJar()
}
}
}
}

repositories {
mavenCentral()
mavenCentral()
}

dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation project(':expo-modules-core')
implementation "com.facebook.react:react-native:0.20.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation "com.google.android.gms:play-services-maps:18.2.0"
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.maps.android:android-maps-utils:3.8.0'
}
12 changes: 10 additions & 2 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
<manifest>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>

</manifest>
154 changes: 117 additions & 37 deletions android/src/main/java/expo/modules/googlemaps/ExpoGoogleMapsModule.kt
Original file line number Diff line number Diff line change
@@ -1,47 +1,127 @@
package expo.modules.googlemaps

import android.annotation.SuppressLint
import android.util.Log
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.model.MapStyleOptions
import expo.modules.googlemaps.views.ExpoMapView
import expo.modules.googlemaps.views.ExpoMarkerView
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import kotlin.math.roundToInt

class ExpoGoogleMapsModule : Module() {
// Each module class must implement the definition function. The definition consists of components
// that describes the module's functionality and behavior.
// See https://docs.expo.dev/modules/module-api for more details about available components.
override fun definition() = ModuleDefinition {
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
// The module will be accessible from `requireNativeModule('ExpoGoogleMaps')` in JavaScript.
Name("ExpoGoogleMaps")

// Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
Constants(
"PI" to Math.PI
)

// Defines event names that the module can send to JavaScript.
Events("onChange")

// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
Function("hello") {
"Hello world! 👋"
}
/**
* Expo module for the markers, since they need to be separate
*/
class ExpoGoogleMapsMarkerModule : Module() {
override fun definition() = ModuleDefinition {
Name("ExpoGoogleMapsMarker")

// Defines a JavaScript function that always returns a Promise and whose native code
// is by default dispatched on the different thread than the JavaScript runtime runs on.
AsyncFunction("setValueAsync") { value: String ->
// Send an event to JavaScript.
sendEvent("onChange", mapOf(
"value" to value
))
View(ExpoMarkerView::class) {
Prop("marker") { view: ExpoMarkerView, marker: MarkerRecord ->
view.updateMarker(marker)
}

Events("onMarkerPress")
}
}
}

class ExpoGoogleMapsModule : Module() {
// Each module class must implement the definition function. The definition consists of components
// that describes the module's functionality and behavior.
// See https://docs.expo.dev/modules/module-api for more details about available components.
@SuppressLint("MissingPermission")
override fun definition() = ModuleDefinition {
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
// The module will be accessible from `requireNativeModule('ExpoGoogleMaps')` in JavaScript.
Name("ExpoGoogleMaps")
Log.d("ExpoGoogleMapsModule", "LOADED")

// Defines event names that the module can send to JavaScript.
Events("log")

Function("setApiKey") { _: String? ->
//No-op
}

// Enables the module to be used as a native view. Definition components that are accepted as part of
// the view definition: Prop, Events.
View(ExpoMapView::class) {
Events("onMapIdle", "onDidChange")

Prop("camera") { view: ExpoMapView, camera: Camera ->
view.camera = camera
}

Prop("polygons") { view: ExpoMapView, polygons: Array<PolygonRecord> ->
view.updatePolygons(polygons.toList())
}

Prop("styleJson") { view: ExpoMapView, styleJson: String? ->
if (view.googleMap == null) {
view.styleJson = styleJson
return@Prop
}
if (styleJson == null) {
view.googleMap?.setMapStyle(null)
return@Prop
}
val success = view.googleMap?.setMapStyle(MapStyleOptions(styleJson))
if (!success!!) {
sendEvent(
"log", mapOf(
"error" to "Failed to set style"
)
)
}
}

Prop("showUserLocation") { view: ExpoMapView, showUserLocation: Boolean? ->
if (view.googleMap == null) {
view.showUserLocation = showUserLocation!!
return@Prop
}
if (showUserLocation == null) {
view.googleMap?.isMyLocationEnabled = false
return@Prop
}
view.googleMap?.isMyLocationEnabled = showUserLocation
}

AsyncFunction("animateCamera") { view: ExpoMapView, camera: Camera, animationOptions: AnimateOptions? ->
if (animationOptions == null) {
view.googleMap?.moveCamera(
CameraUpdateFactory.newCameraPosition(camera.toGmsCameraPosition())
)
} else {
view.googleMap?.animateCamera(
CameraUpdateFactory.newCameraPosition(camera.toGmsCameraPosition()),
(animationOptions.animationDuration * 1000).roundToInt(),
null
)
}
}

// Enables the module to be used as a native view. Definition components that are accepted as part of
// the view definition: Prop, Events.
View(ExpoGoogleMapsView::class) {
// Defines a setter for the `name` prop.
Prop("name") { view: ExpoGoogleMapsView, prop: String ->
println(prop)
}
AsyncFunction("fitToBounds") { view: ExpoMapView, params: FitToBoundsParams, animationOptions: AnimateOptions? ->
val topRight = params.topRight.toLatLng()
val bottomLeft = params.bottomLeft.toLatLng()
var padding = 0
if (params.insets != null) {
padding = arrayOf(
params.insets.top.roundToInt(),
params.insets.left.roundToInt(),
params.insets.bottom.roundToInt(),
params.insets.right.roundToInt()
).max()
}
if (animationOptions == null) {
view.fitToBounds(topRight, bottomLeft, padding, AnimateOptions())
} else {
view.fitToBounds(topRight, bottomLeft, padding, animationOptions)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package expo.modules.googlemaps

import android.content.Context
import expo.modules.core.interfaces.Package
import expo.modules.core.interfaces.ReactActivityLifecycleListener

class ExpoGoogleMapsPackage : Package {
override fun createReactActivityLifecycleListeners(activityContext: Context?): List<ReactActivityLifecycleListener> {
return listOf(ExpoGoogleMapsReactActivityLifecycleListener())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package expo.modules.googlemaps

import android.app.Activity
import expo.modules.core.interfaces.ReactActivityLifecycleListener
import expo.modules.googlemaps.views.ExpoMapView

class ExpoGoogleMapsReactActivityLifecycleListener : ReactActivityLifecycleListener {
override fun onPause(activity: Activity?) {
super.onPause(activity)
ExpoMapView.mapView?.onPause()
}

override fun onResume(activity: Activity?) {
super.onResume(activity)
ExpoMapView.mapView?.onResume()
}

override fun onDestroy(activity: Activity?) {
super.onDestroy(activity)
ExpoMapView.mapView?.onStop()
ExpoMapView.mapView?.onDestroy()
}
}
Loading

0 comments on commit 7afa151

Please sign in to comment.