Skip to content

Commit 0f4cec0

Browse files
committed
refactor(android): lifecycle
1 parent fd8b8da commit 0f4cec0

File tree

3 files changed

+148
-93
lines changed

3 files changed

+148
-93
lines changed

android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt

Lines changed: 64 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import android.location.Location
1212
import android.util.Size
1313
import android.view.View
1414
import android.widget.FrameLayout
15-
import com.facebook.react.bridge.LifecycleEventListener
15+
import androidx.lifecycle.Lifecycle
16+
import androidx.lifecycle.findViewTreeLifecycleOwner
1617
import com.facebook.react.uimanager.PixelUtil.dpToPx
1718
import com.facebook.react.uimanager.ThemedReactContext
1819
import com.google.android.gms.maps.CameraUpdateFactory
@@ -81,10 +82,12 @@ class GoogleMapsViewImpl(
8182
GoogleMap.OnInfoWindowLongClickListener,
8283
GoogleMap.OnMyLocationClickListener,
8384
GoogleMap.OnMyLocationButtonClickListener,
84-
GoogleMap.InfoWindowAdapter,
85-
LifecycleEventListener {
86-
private var initialized = false
87-
private var loaded = false
85+
GoogleMap.InfoWindowAdapter {
86+
private var lifecycleObserver: MapLifecycleEventObserver? = null
87+
private var lifecycle: Lifecycle? = null
88+
89+
private var mapViewInitialized = false
90+
private var mapViewLoaded = false
8891
private var destroyed = false
8992
private var googleMap: GoogleMap? = null
9093
private var mapView: MapView? = null
@@ -125,13 +128,12 @@ class GoogleMapsViewImpl(
125128
init {
126129
MapsInitializer.initialize(reactContext)
127130
reactContext.registerComponentCallbacks(componentCallbacks)
128-
reactContext.addLifecycleEventListener(this)
129131
}
130132

131-
fun initMapView(googleMapsOptions: GoogleMapOptions) =
133+
fun initMapView() =
132134
onUi {
133-
if (initialized) return@onUi
134-
initialized = true
135+
if (mapViewInitialized) return@onUi
136+
mapViewInitialized = true
135137

136138
val result = playServiceHandler.playServicesAvailability()
137139
val errorCode = result.toRNMapErrorCodeOrNull()
@@ -144,45 +146,46 @@ class GoogleMapsViewImpl(
144146
}
145147
}
146148

147-
mapView = MapView(reactContext, googleMapsOptions)
148-
super.addView(mapView)
149-
150-
mapView?.onCreate(null)
151-
mapView?.getMapAsync { map ->
152-
googleMap = map
153-
googleMap?.setOnMapLoadedCallback {
154-
googleMap?.setOnCameraMoveStartedListener(this@GoogleMapsViewImpl)
155-
googleMap?.setOnCameraMoveListener(this@GoogleMapsViewImpl)
156-
googleMap?.setOnCameraIdleListener(this@GoogleMapsViewImpl)
157-
googleMap?.setOnMarkerClickListener(this@GoogleMapsViewImpl)
158-
googleMap?.setOnPolylineClickListener(this@GoogleMapsViewImpl)
159-
googleMap?.setOnPolygonClickListener(this@GoogleMapsViewImpl)
160-
googleMap?.setOnCircleClickListener(this@GoogleMapsViewImpl)
161-
googleMap?.setOnMapClickListener(this@GoogleMapsViewImpl)
162-
googleMap?.setOnMapLongClickListener(this@GoogleMapsViewImpl)
163-
googleMap?.setOnPoiClickListener(this@GoogleMapsViewImpl)
164-
googleMap?.setOnMarkerDragListener(this@GoogleMapsViewImpl)
165-
googleMap?.setOnInfoWindowClickListener(this@GoogleMapsViewImpl)
166-
googleMap?.setOnInfoWindowCloseListener(this@GoogleMapsViewImpl)
167-
googleMap?.setOnInfoWindowLongClickListener(this@GoogleMapsViewImpl)
168-
googleMap?.setOnMyLocationClickListener(this@GoogleMapsViewImpl)
169-
googleMap?.setOnMyLocationButtonClickListener(this@GoogleMapsViewImpl)
170-
googleMap?.setInfoWindowAdapter(this@GoogleMapsViewImpl)
171-
loaded = true
172-
onMapLoaded?.invoke(
173-
map.projection.visibleRegion.toRnRegion(),
174-
map.cameraPosition.toRnCamera(),
175-
)
149+
mapView =
150+
MapView(reactContext, googleMapsOptions).also {
151+
lifecycleObserver = MapLifecycleEventObserver(it, locationHandler)
152+
super.addView(it)
153+
it.getMapAsync { map ->
154+
googleMap = map
155+
googleMap?.setOnMapLoadedCallback {
156+
googleMap?.setOnCameraMoveStartedListener(this@GoogleMapsViewImpl)
157+
googleMap?.setOnCameraMoveListener(this@GoogleMapsViewImpl)
158+
googleMap?.setOnCameraIdleListener(this@GoogleMapsViewImpl)
159+
googleMap?.setOnMarkerClickListener(this@GoogleMapsViewImpl)
160+
googleMap?.setOnPolylineClickListener(this@GoogleMapsViewImpl)
161+
googleMap?.setOnPolygonClickListener(this@GoogleMapsViewImpl)
162+
googleMap?.setOnCircleClickListener(this@GoogleMapsViewImpl)
163+
googleMap?.setOnMapClickListener(this@GoogleMapsViewImpl)
164+
googleMap?.setOnMapLongClickListener(this@GoogleMapsViewImpl)
165+
googleMap?.setOnPoiClickListener(this@GoogleMapsViewImpl)
166+
googleMap?.setOnMarkerDragListener(this@GoogleMapsViewImpl)
167+
googleMap?.setOnInfoWindowClickListener(this@GoogleMapsViewImpl)
168+
googleMap?.setOnInfoWindowCloseListener(this@GoogleMapsViewImpl)
169+
googleMap?.setOnInfoWindowLongClickListener(this@GoogleMapsViewImpl)
170+
googleMap?.setOnMyLocationClickListener(this@GoogleMapsViewImpl)
171+
googleMap?.setOnMyLocationButtonClickListener(this@GoogleMapsViewImpl)
172+
googleMap?.setInfoWindowAdapter(this@GoogleMapsViewImpl)
173+
mapViewLoaded = true
174+
onMapLoaded?.invoke(
175+
map.projection.visibleRegion.toRnRegion(),
176+
map.cameraPosition.toRnCamera(),
177+
)
178+
}
179+
applyProps()
180+
initLocationCallbacks()
181+
onMapReady?.invoke(true)
182+
}
176183
}
177-
applyProps()
178-
initLocationCallbacks()
179-
onMapReady?.invoke(true)
180-
}
181184
}
182185

183186
override fun onCameraMoveStarted(reason: Int) =
184187
onUi {
185-
if (!loaded) return@onUi
188+
if (!mapViewLoaded) return@onUi
186189
cameraMoveReason = reason
187190
val visibleRegion = googleMap?.projection?.visibleRegion ?: return@onUi
188191
val cameraPosition = googleMap?.cameraPosition ?: return@onUi
@@ -195,7 +198,7 @@ class GoogleMapsViewImpl(
195198

196199
override fun onCameraMove() =
197200
onUi {
198-
if (!loaded) return@onUi
201+
if (!mapViewLoaded) return@onUi
199202
val visibleRegion = googleMap?.projection?.visibleRegion ?: return@onUi
200203
val cameraPosition = googleMap?.cameraPosition ?: return@onUi
201204
val gesture = GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE == cameraMoveReason
@@ -208,7 +211,7 @@ class GoogleMapsViewImpl(
208211

209212
override fun onCameraIdle() =
210213
onUi {
211-
if (!loaded) return@onUi
214+
if (!mapViewLoaded) return@onUi
212215
val visibleRegion = googleMap?.projection?.visibleRegion ?: return@onUi
213216
val cameraPosition = googleMap?.cameraPosition ?: return@onUi
214217
val gesture = GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE == cameraMoveReason
@@ -226,7 +229,6 @@ class GoogleMapsViewImpl(
226229
locationHandler.onError = { error ->
227230
onUi { onLocationError?.invoke(error) }
228231
}
229-
locationHandler.start()
230232
}
231233

232234
fun applyProps() {
@@ -275,7 +277,7 @@ class GoogleMapsViewImpl(
275277
val currentCamera: CameraPosition?
276278
get() = onUiSync { googleMap?.cameraPosition }
277279

278-
var initialProps: RNInitialProps? = null
280+
var googleMapsOptions: GoogleMapOptions = GoogleMapOptions()
279281

280282
var uiSettings: RNMapUiSettings? = null
281283
set(value) {
@@ -811,7 +813,7 @@ class GoogleMapsViewImpl(
811813
onUi {
812814
if (destroyed) return@onUi
813815
destroyed = true
814-
locationHandler.stop()
816+
lifecycleObserver?.toDestroyedState()
815817
markerBuilder.cancelAllJobs()
816818
clearMarkers()
817819
clearPolylines()
@@ -840,16 +842,10 @@ class GoogleMapsViewImpl(
840842
setInfoWindowAdapter(null)
841843
}
842844
googleMap = null
843-
mapView?.apply {
844-
onPause()
845-
onStop()
846-
onDestroy()
847-
removeAllViews()
848-
}
845+
mapView?.removeAllViews()
849846
super.removeAllViews()
850847
reactContext.unregisterComponentCallbacks(componentCallbacks)
851-
reactContext.removeLifecycleEventListener(this)
852-
initialized = false
848+
mapViewInitialized = false
853849
}
854850

855851
override fun requestLayout() {
@@ -864,32 +860,20 @@ class GoogleMapsViewImpl(
864860
}
865861
}
866862

867-
override fun onAttachedToWindow() =
868-
onUi {
869-
super.onAttachedToWindow()
870-
locationHandler.start()
871-
}
872-
873-
override fun onDetachedFromWindow() =
874-
onUi {
875-
super.onDetachedFromWindow()
876-
locationHandler.stop()
877-
}
878-
879-
override fun onHostResume() =
880-
onUi {
881-
locationHandler.start()
882-
mapView?.onResume()
883-
}
884-
885-
override fun onHostPause() =
886-
onUi {
887-
locationHandler.stop()
888-
mapView?.onPause()
863+
override fun onAttachedToWindow() {
864+
super.onAttachedToWindow()
865+
initMapView()
866+
lifecycle = mapView?.findViewTreeLifecycleOwner()?.lifecycle
867+
lifecycleObserver?.let { observer ->
868+
lifecycle?.addObserver(observer)
889869
}
870+
}
890871

891-
override fun onHostDestroy() {
892-
destroyInternal()
872+
override fun onDetachedFromWindow() {
873+
super.onDetachedFromWindow()
874+
lifecycleObserver?.let { lifecycle?.removeObserver(it) }
875+
lifecycle = null
876+
lifecycleObserver?.toCreatedState()
893877
}
894878

895879
override fun onMarkerClick(marker: Marker): Boolean {
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.rngooglemapsplus
2+
3+
import android.os.Bundle
4+
import androidx.lifecycle.Lifecycle
5+
import androidx.lifecycle.LifecycleEventObserver
6+
import androidx.lifecycle.LifecycleOwner
7+
import com.google.android.gms.maps.MapView
8+
9+
class MapLifecycleEventObserver(
10+
private val mapView: MapView?,
11+
private val locationHandler: LocationHandler,
12+
) : LifecycleEventObserver {
13+
private var currentState: Lifecycle.State = Lifecycle.State.INITIALIZED
14+
15+
override fun onStateChanged(
16+
source: LifecycleOwner,
17+
event: Lifecycle.Event,
18+
) {
19+
when (event) {
20+
Lifecycle.Event.ON_DESTROY -> toCreatedState()
21+
else -> toState(event.targetState)
22+
}
23+
}
24+
25+
fun toCreatedState() {
26+
if (currentState > Lifecycle.State.CREATED) {
27+
toState(Lifecycle.State.CREATED)
28+
}
29+
}
30+
31+
fun toDestroyedState() {
32+
if (currentState > Lifecycle.State.INITIALIZED) {
33+
toState(Lifecycle.State.DESTROYED)
34+
}
35+
}
36+
37+
private fun toState(state: Lifecycle.State) {
38+
while (currentState != state) {
39+
when {
40+
currentState < state -> upFromCurrentState()
41+
currentState > state -> downFromCurrentState()
42+
}
43+
}
44+
}
45+
46+
private fun downFromCurrentState() {
47+
Lifecycle.Event.downFrom(currentState)?.also {
48+
invokeEvent(it)
49+
}
50+
}
51+
52+
private fun upFromCurrentState() {
53+
Lifecycle.Event.upFrom(currentState)?.also {
54+
invokeEvent(it)
55+
}
56+
}
57+
58+
private fun invokeEvent(event: Lifecycle.Event) {
59+
when (event) {
60+
Lifecycle.Event.ON_CREATE -> mapView?.onCreate(Bundle())
61+
Lifecycle.Event.ON_START -> mapView?.onStart()
62+
Lifecycle.Event.ON_RESUME -> {
63+
locationHandler.start()
64+
mapView?.onResume()
65+
}
66+
67+
Lifecycle.Event.ON_PAUSE -> {
68+
mapView?.onPause()
69+
locationHandler.stop()
70+
}
71+
72+
Lifecycle.Event.ON_STOP -> mapView?.onStop()
73+
Lifecycle.Event.ON_DESTROY -> mapView?.onDestroy()
74+
Lifecycle.Event.ON_ANY -> {}
75+
}
76+
currentState = event.targetState
77+
}
78+
}

android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import com.rngooglemapsplus.extensions.toSize
2424
class RNGoogleMapsPlusView(
2525
val context: ThemedReactContext,
2626
) : HybridRNGoogleMapsPlusViewSpec() {
27-
private var propsInitialized = false
2827
private var currentCustomMapStyle: String? = null
2928
private var permissionHandler = PermissionHandler(context)
3029
private var locationHandler = LocationHandler(context)
@@ -40,26 +39,20 @@ class RNGoogleMapsPlusView(
4039
override val view =
4140
GoogleMapsViewImpl(context, locationHandler, playServiceHandler, markerBuilder)
4241

43-
override fun afterUpdate() {
44-
super.afterUpdate()
45-
if (!propsInitialized) {
46-
propsInitialized = true
42+
override var initialProps: RNInitialProps? = null
43+
set(value) {
44+
if (field == value) return
45+
field = value
46+
4747
val options =
4848
GoogleMapOptions().apply {
4949
initialProps?.mapId?.let { mapId(it) }
5050
initialProps?.liteMode?.let { liteMode(it) }
5151
initialProps?.camera?.let { camera(it.toCameraPosition(current = null)) }
5252
initialProps?.backgroundColor?.let { backgroundColor(it.toColor()) }
5353
}
54-
view.initMapView(options)
55-
}
56-
}
5754

58-
override var initialProps: RNInitialProps? = null
59-
set(value) {
60-
if (field == value) return
61-
field = value
62-
view.initialProps = value
55+
view.googleMapsOptions = options
6356
}
6457

6558
override var uiSettings: RNMapUiSettings? = null

0 commit comments

Comments
 (0)