diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapView.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapView.kt index 8f177a23..cc616bb4 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapView.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapView.kt @@ -40,8 +40,8 @@ internal constructor( // Call all of these three lifecycle functions in sequence to fully // initialize the map view. _mapView.onCreate(context.applicationInfo.metaData) - _mapView.onStart() - _mapView.onResume() + onStart() + onResume() viewRegistry.registerMapView(viewId, this) @@ -55,28 +55,49 @@ internal constructor( } override fun dispose() { + if (super.isDestroyed()) { + return + } + + viewRegistry.unregisterNavigationView(getViewId()) + // When view is disposed, all of these lifecycle functions must be // called to properly dispose navigation view and prevent leaks. - _mapView.onPause() - _mapView.onStop() + onPause() + onStop() + super.onDispose() _mapView.onDestroy() - - viewRegistry.unregisterMapView(getViewId()) } - override fun onStart() { - _mapView.onStart() + override fun onStart(): Boolean { + if (super.onStart()) { + _mapView.onStart() + return true + } + return false } - override fun onResume() { - _mapView.onResume() + override fun onResume(): Boolean { + if (super.onResume()) { + _mapView.onResume() + return true + } + return false } - override fun onStop() { - _mapView.onStop() + override fun onStop(): Boolean { + if (super.onStop()) { + _mapView.onStop() + return true + } + return false } - override fun onPause() { - _mapView.onPause() + override fun onPause(): Boolean { + if (super.onPause()) { + _mapView.onPause() + return true + } + return false } } diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsAutoMapView.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsAutoMapView.kt index fe77e96b..c50429db 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsAutoMapView.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsAutoMapView.kt @@ -41,14 +41,19 @@ internal constructor( } // Handled by AndroidAutoBaseScreen. - override fun onStart() {} + override fun onStart(): Boolean { + return super.onStart() + } - // Handled by AndroidAutoBaseScreen. - override fun onResume() {} + override fun onResume(): Boolean { + return super.onResume() + } - // Handled by AndroidAutoBaseScreen. - override fun onStop() {} + override fun onStop(): Boolean { + return super.onStop() + } - // Handled by AndroidAutoBaseScreen. - override fun onPause() {} + override fun onPause(): Boolean { + return super.onPause() + } } diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt index 42f8fa27..5f790424 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt @@ -61,18 +61,69 @@ abstract class GoogleMapsBaseMapView( private var _mapReadyCallback: ((Result) -> Unit)? = null private var _pendingCameraEventsListenerSetup = false - /// Default values for UI features. + // Default values for UI features. private var _consumeMyLocationButtonClickEventsEnabled: Boolean = false + // View lifecycle states. + private enum class LifecycleState { + NONE, + STARTED, + RESUMED, + PAUSED, + STOPPED, + DESTROYED, + } + + // Current lifecycle state tracks the state of the view. + // This is used to avoid calling lifecycle methods in the wrong order. + private var currentLifecycleState: LifecycleState = LifecycleState.NONE + abstract fun getView(): View - abstract fun onStart() + open fun onStart(): Boolean { + if ( + currentLifecycleState == LifecycleState.STOPPED || + currentLifecycleState == LifecycleState.NONE + ) { + currentLifecycleState = LifecycleState.STARTED + return true + } + return false + } - abstract fun onResume() + open fun onResume(): Boolean { + if ( + currentLifecycleState == LifecycleState.STARTED || + currentLifecycleState == LifecycleState.PAUSED + ) { + currentLifecycleState = LifecycleState.RESUMED + return true + } + return false + } - abstract fun onStop() + open fun onStop(): Boolean { + if ( + currentLifecycleState == LifecycleState.PAUSED || + currentLifecycleState == LifecycleState.STARTED + ) { + currentLifecycleState = LifecycleState.STOPPED + return true + } + return false + } - abstract fun onPause() + open fun onPause(): Boolean { + if (currentLifecycleState == LifecycleState.RESUMED) { + currentLifecycleState = LifecycleState.PAUSED + return true + } + return false + } + + protected fun isDestroyed(): Boolean { + return currentLifecycleState == LifecycleState.DESTROYED + } // Method to set the _map object protected fun setMap(map: GoogleMap) { @@ -218,6 +269,33 @@ abstract class GoogleMapsBaseMapView( } } + protected open fun onDispose() { + getMap().run { + setOnMapClickListener(null) + setOnMapLongClickListener(null) + setOnMarkerClickListener(null) + setOnMarkerDragListener(null) + setOnInfoWindowClickListener(null) + setOnInfoWindowClickListener(null) + setOnInfoWindowLongClickListener(null) + setOnPolygonClickListener(null) + setOnPolylineClickListener(null) + setOnMyLocationClickListener(null) + setOnMyLocationButtonClickListener(null) + setOnFollowMyLocationCallback(null) + setOnCameraMoveStartedListener(null) + setOnCameraMoveListener(null) + setOnCameraIdleListener(null) + } + + // Clear surfaceTextureListener + val textureView = findTextureView(getView()) ?: return + textureView.surfaceTextureListener = null + _map = null + + currentLifecycleState = LifecycleState.DESTROYED + } + // Installs a custom invalidator for the map view. private fun installInvalidator() { val textureView = findTextureView(getView()) ?: return diff --git a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt index 710b9f35..605c2828 100644 --- a/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt +++ b/android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationView.kt @@ -21,6 +21,7 @@ import android.content.res.Configuration import android.view.View import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.libraries.navigation.NavigationView +import com.google.android.libraries.navigation.OnNavigationUiChangedListener import io.flutter.plugin.platform.PlatformView class GoogleMapsNavigationView @@ -46,6 +47,10 @@ internal constructor( private var _isReportIncidentButtonEnabled: Boolean = true private var _isTrafficPromptsEnabled: Boolean = true + private var _onRecenterButtonClickedListener: NavigationView.OnRecenterButtonClickedListener? = + null + private var _onNavigationUIEnabledChanged: OnNavigationUiChangedListener? = null + override fun getView(): View { return _navigationView } @@ -54,8 +59,8 @@ internal constructor( // Call all of these three lifecycle functions in sequence to fully // initialize the navigation view. _navigationView.onCreate(context.applicationInfo.metaData) - _navigationView.onStart() - _navigationView.onResume() + onStart() + onResume() // Initialize navigation view with given navigation view options var navigationViewEnabled = false @@ -91,42 +96,60 @@ internal constructor( } override fun dispose() { - getMap().setOnMapClickListener(null) - getMap().setOnMapLongClickListener(null) - getMap().setOnMarkerClickListener(null) - getMap().setOnMarkerDragListener(null) - getMap().setOnInfoWindowClickListener(null) - getMap().setOnInfoWindowClickListener(null) - getMap().setOnInfoWindowLongClickListener(null) - getMap().setOnPolygonClickListener(null) - getMap().setOnPolylineClickListener(null) + if (super.isDestroyed()) { + return + } + + viewRegistry.unregisterNavigationView(getViewId()) + + // Remove navigation view specific listeners + if (_onRecenterButtonClickedListener != null) { + _navigationView.removeOnRecenterButtonClickedListener(_onRecenterButtonClickedListener) + _onRecenterButtonClickedListener = null + } + if (_onNavigationUIEnabledChanged != null) { + _navigationView.removeOnNavigationUiChangedListener(_onNavigationUIEnabledChanged) + _onNavigationUIEnabledChanged = null + } // When view is disposed, all of these lifecycle functions must be // called to properly dispose navigation view and prevent leaks. - _navigationView.isNavigationUiEnabled = false - _navigationView.onPause() - _navigationView.onStop() + onPause() + onStop() + super.onDispose() _navigationView.onDestroy() - - _navigationView.removeOnRecenterButtonClickedListener {} - - viewRegistry.unregisterNavigationView(getViewId()) } - override fun onStart() { - _navigationView.onStart() + override fun onStart(): Boolean { + if (super.onStart()) { + _navigationView.onStart() + return true + } + return false } - override fun onResume() { - _navigationView.onResume() + override fun onResume(): Boolean { + if (super.onResume()) { + _navigationView.onResume() + return true + } + return false } - override fun onStop() { - _navigationView.onStop() + override fun onStop(): Boolean { + if (super.onStop()) { + _navigationView.onStop() + return true + } + return false } - override fun onPause() { - _navigationView.onPause() + override fun onPause(): Boolean { + if (super.onPause()) { + _navigationView.onPause() + return true + } + return false } fun onConfigurationChanged(configuration: Configuration) { @@ -138,12 +161,17 @@ internal constructor( } override fun initListeners() { - _navigationView.addOnRecenterButtonClickedListener { - viewEventApi?.onRecenterButtonClicked(getViewId().toLong()) {} - } - _navigationView.addOnNavigationUiChangedListener { + _onRecenterButtonClickedListener = + NavigationView.OnRecenterButtonClickedListener { + viewEventApi?.onRecenterButtonClicked(getViewId().toLong()) {} + } + _navigationView.addOnRecenterButtonClickedListener(_onRecenterButtonClickedListener) + + _onNavigationUIEnabledChanged = OnNavigationUiChangedListener { viewEventApi?.onNavigationUIEnabledChanged(getViewId().toLong(), it) {} } + _navigationView.addOnNavigationUiChangedListener(_onNavigationUIEnabledChanged) + super.initListeners() }