Skip to content

Commit 9774479

Browse files
authored
fix: view dispose crash on android if view is on background (#453)
* fix: view dispose crash on android if view is on background * chore: format kotlin code
1 parent 9eeb105 commit 9774479

File tree

4 files changed

+188
-56
lines changed

4 files changed

+188
-56
lines changed

android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapView.kt

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ internal constructor(
4040
// Call all of these three lifecycle functions in sequence to fully
4141
// initialize the map view.
4242
_mapView.onCreate(context.applicationInfo.metaData)
43-
_mapView.onStart()
44-
_mapView.onResume()
43+
onStart()
44+
onResume()
4545

4646
viewRegistry.registerMapView(viewId, this)
4747

@@ -55,28 +55,49 @@ internal constructor(
5555
}
5656

5757
override fun dispose() {
58+
if (super.isDestroyed()) {
59+
return
60+
}
61+
62+
viewRegistry.unregisterNavigationView(getViewId())
63+
5864
// When view is disposed, all of these lifecycle functions must be
5965
// called to properly dispose navigation view and prevent leaks.
60-
_mapView.onPause()
61-
_mapView.onStop()
66+
onPause()
67+
onStop()
68+
super.onDispose()
6269
_mapView.onDestroy()
63-
64-
viewRegistry.unregisterMapView(getViewId())
6570
}
6671

67-
override fun onStart() {
68-
_mapView.onStart()
72+
override fun onStart(): Boolean {
73+
if (super.onStart()) {
74+
_mapView.onStart()
75+
return true
76+
}
77+
return false
6978
}
7079

71-
override fun onResume() {
72-
_mapView.onResume()
80+
override fun onResume(): Boolean {
81+
if (super.onResume()) {
82+
_mapView.onResume()
83+
return true
84+
}
85+
return false
7386
}
7487

75-
override fun onStop() {
76-
_mapView.onStop()
88+
override fun onStop(): Boolean {
89+
if (super.onStop()) {
90+
_mapView.onStop()
91+
return true
92+
}
93+
return false
7794
}
7895

79-
override fun onPause() {
80-
_mapView.onPause()
96+
override fun onPause(): Boolean {
97+
if (super.onPause()) {
98+
_mapView.onPause()
99+
return true
100+
}
101+
return false
81102
}
82103
}

android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsAutoMapView.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,19 @@ internal constructor(
4141
}
4242

4343
// Handled by AndroidAutoBaseScreen.
44-
override fun onStart() {}
44+
override fun onStart(): Boolean {
45+
return super.onStart()
46+
}
4547

46-
// Handled by AndroidAutoBaseScreen.
47-
override fun onResume() {}
48+
override fun onResume(): Boolean {
49+
return super.onResume()
50+
}
4851

49-
// Handled by AndroidAutoBaseScreen.
50-
override fun onStop() {}
52+
override fun onStop(): Boolean {
53+
return super.onStop()
54+
}
5155

52-
// Handled by AndroidAutoBaseScreen.
53-
override fun onPause() {}
56+
override fun onPause(): Boolean {
57+
return super.onPause()
58+
}
5459
}

android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,69 @@ abstract class GoogleMapsBaseMapView(
6161
private var _mapReadyCallback: ((Result<Unit>) -> Unit)? = null
6262
private var _pendingCameraEventsListenerSetup = false
6363

64-
/// Default values for UI features.
64+
// Default values for UI features.
6565
private var _consumeMyLocationButtonClickEventsEnabled: Boolean = false
6666

67+
// View lifecycle states.
68+
private enum class LifecycleState {
69+
NONE,
70+
STARTED,
71+
RESUMED,
72+
PAUSED,
73+
STOPPED,
74+
DESTROYED,
75+
}
76+
77+
// Current lifecycle state tracks the state of the view.
78+
// This is used to avoid calling lifecycle methods in the wrong order.
79+
private var currentLifecycleState: LifecycleState = LifecycleState.NONE
80+
6781
abstract fun getView(): View
6882

69-
abstract fun onStart()
83+
open fun onStart(): Boolean {
84+
if (
85+
currentLifecycleState == LifecycleState.STOPPED ||
86+
currentLifecycleState == LifecycleState.NONE
87+
) {
88+
currentLifecycleState = LifecycleState.STARTED
89+
return true
90+
}
91+
return false
92+
}
7093

71-
abstract fun onResume()
94+
open fun onResume(): Boolean {
95+
if (
96+
currentLifecycleState == LifecycleState.STARTED ||
97+
currentLifecycleState == LifecycleState.PAUSED
98+
) {
99+
currentLifecycleState = LifecycleState.RESUMED
100+
return true
101+
}
102+
return false
103+
}
72104

73-
abstract fun onStop()
105+
open fun onStop(): Boolean {
106+
if (
107+
currentLifecycleState == LifecycleState.PAUSED ||
108+
currentLifecycleState == LifecycleState.STARTED
109+
) {
110+
currentLifecycleState = LifecycleState.STOPPED
111+
return true
112+
}
113+
return false
114+
}
74115

75-
abstract fun onPause()
116+
open fun onPause(): Boolean {
117+
if (currentLifecycleState == LifecycleState.RESUMED) {
118+
currentLifecycleState = LifecycleState.PAUSED
119+
return true
120+
}
121+
return false
122+
}
123+
124+
protected fun isDestroyed(): Boolean {
125+
return currentLifecycleState == LifecycleState.DESTROYED
126+
}
76127

77128
// Method to set the _map object
78129
protected fun setMap(map: GoogleMap) {
@@ -218,6 +269,33 @@ abstract class GoogleMapsBaseMapView(
218269
}
219270
}
220271

272+
protected open fun onDispose() {
273+
getMap().run {
274+
setOnMapClickListener(null)
275+
setOnMapLongClickListener(null)
276+
setOnMarkerClickListener(null)
277+
setOnMarkerDragListener(null)
278+
setOnInfoWindowClickListener(null)
279+
setOnInfoWindowClickListener(null)
280+
setOnInfoWindowLongClickListener(null)
281+
setOnPolygonClickListener(null)
282+
setOnPolylineClickListener(null)
283+
setOnMyLocationClickListener(null)
284+
setOnMyLocationButtonClickListener(null)
285+
setOnFollowMyLocationCallback(null)
286+
setOnCameraMoveStartedListener(null)
287+
setOnCameraMoveListener(null)
288+
setOnCameraIdleListener(null)
289+
}
290+
291+
// Clear surfaceTextureListener
292+
val textureView = findTextureView(getView()) ?: return
293+
textureView.surfaceTextureListener = null
294+
_map = null
295+
296+
currentLifecycleState = LifecycleState.DESTROYED
297+
}
298+
221299
// Installs a custom invalidator for the map view.
222300
private fun installInvalidator() {
223301
val textureView = findTextureView(getView()) ?: return

android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.content.res.Configuration
2121
import android.view.View
2222
import com.google.android.gms.maps.CameraUpdateFactory
2323
import com.google.android.libraries.navigation.NavigationView
24+
import com.google.android.libraries.navigation.OnNavigationUiChangedListener
2425
import io.flutter.plugin.platform.PlatformView
2526

2627
class GoogleMapsNavigationView
@@ -46,6 +47,10 @@ internal constructor(
4647
private var _isReportIncidentButtonEnabled: Boolean = true
4748
private var _isTrafficPromptsEnabled: Boolean = true
4849

50+
private var _onRecenterButtonClickedListener: NavigationView.OnRecenterButtonClickedListener? =
51+
null
52+
private var _onNavigationUIEnabledChanged: OnNavigationUiChangedListener? = null
53+
4954
override fun getView(): View {
5055
return _navigationView
5156
}
@@ -54,8 +59,8 @@ internal constructor(
5459
// Call all of these three lifecycle functions in sequence to fully
5560
// initialize the navigation view.
5661
_navigationView.onCreate(context.applicationInfo.metaData)
57-
_navigationView.onStart()
58-
_navigationView.onResume()
62+
onStart()
63+
onResume()
5964

6065
// Initialize navigation view with given navigation view options
6166
var navigationViewEnabled = false
@@ -91,42 +96,60 @@ internal constructor(
9196
}
9297

9398
override fun dispose() {
94-
getMap().setOnMapClickListener(null)
95-
getMap().setOnMapLongClickListener(null)
96-
getMap().setOnMarkerClickListener(null)
97-
getMap().setOnMarkerDragListener(null)
98-
getMap().setOnInfoWindowClickListener(null)
99-
getMap().setOnInfoWindowClickListener(null)
100-
getMap().setOnInfoWindowLongClickListener(null)
101-
getMap().setOnPolygonClickListener(null)
102-
getMap().setOnPolylineClickListener(null)
99+
if (super.isDestroyed()) {
100+
return
101+
}
102+
103+
viewRegistry.unregisterNavigationView(getViewId())
104+
105+
// Remove navigation view specific listeners
106+
if (_onRecenterButtonClickedListener != null) {
107+
_navigationView.removeOnRecenterButtonClickedListener(_onRecenterButtonClickedListener)
108+
_onRecenterButtonClickedListener = null
109+
}
110+
if (_onNavigationUIEnabledChanged != null) {
111+
_navigationView.removeOnNavigationUiChangedListener(_onNavigationUIEnabledChanged)
112+
_onNavigationUIEnabledChanged = null
113+
}
103114

104115
// When view is disposed, all of these lifecycle functions must be
105116
// called to properly dispose navigation view and prevent leaks.
106-
_navigationView.isNavigationUiEnabled = false
107-
_navigationView.onPause()
108-
_navigationView.onStop()
117+
onPause()
118+
onStop()
119+
super.onDispose()
109120
_navigationView.onDestroy()
110-
111-
_navigationView.removeOnRecenterButtonClickedListener {}
112-
113-
viewRegistry.unregisterNavigationView(getViewId())
114121
}
115122

116-
override fun onStart() {
117-
_navigationView.onStart()
123+
override fun onStart(): Boolean {
124+
if (super.onStart()) {
125+
_navigationView.onStart()
126+
return true
127+
}
128+
return false
118129
}
119130

120-
override fun onResume() {
121-
_navigationView.onResume()
131+
override fun onResume(): Boolean {
132+
if (super.onResume()) {
133+
_navigationView.onResume()
134+
return true
135+
}
136+
return false
122137
}
123138

124-
override fun onStop() {
125-
_navigationView.onStop()
139+
override fun onStop(): Boolean {
140+
if (super.onStop()) {
141+
_navigationView.onStop()
142+
return true
143+
}
144+
return false
126145
}
127146

128-
override fun onPause() {
129-
_navigationView.onPause()
147+
override fun onPause(): Boolean {
148+
if (super.onPause()) {
149+
_navigationView.onPause()
150+
return true
151+
}
152+
return false
130153
}
131154

132155
fun onConfigurationChanged(configuration: Configuration) {
@@ -138,12 +161,17 @@ internal constructor(
138161
}
139162

140163
override fun initListeners() {
141-
_navigationView.addOnRecenterButtonClickedListener {
142-
viewEventApi?.onRecenterButtonClicked(getViewId().toLong()) {}
143-
}
144-
_navigationView.addOnNavigationUiChangedListener {
164+
_onRecenterButtonClickedListener =
165+
NavigationView.OnRecenterButtonClickedListener {
166+
viewEventApi?.onRecenterButtonClicked(getViewId().toLong()) {}
167+
}
168+
_navigationView.addOnRecenterButtonClickedListener(_onRecenterButtonClickedListener)
169+
170+
_onNavigationUIEnabledChanged = OnNavigationUiChangedListener {
145171
viewEventApi?.onNavigationUIEnabledChanged(getViewId().toLong(), it) {}
146172
}
173+
_navigationView.addOnNavigationUiChangedListener(_onNavigationUIEnabledChanged)
174+
147175
super.initListeners()
148176
}
149177

0 commit comments

Comments
 (0)