You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Android OS version: Android Versions 11, 12 and 13
Devices affected: Tested on two real devices and one simulator
Maps SDK Version: 2.17.6
Observed behavior and steps to reproduce
I have used Map Box Navigation to show the navigation directions from one location to another location using Android View in Jetpack Compose. After going back to previous screen I also removed the Map Box Screen from back stack of Jetpack Compose. But after removing the screen, memory space captured by Map Box using Android View Jetpack Compose is not freeing up. This is causing my android application to work slow. Please try to fix this issue. Below are the details with Evidences.
//On Back press unregistering the location observer and clearing the current location values
BackHandler {
locationObserver.value?.let { location ->
mapBoxNavigation.value?.unregisterLocationObserver(location)
}
currentLongitude.value = null
currentLongitude.value = null
navController.navigate(HOSPITAL_ROUTE_SCREEN) {
popUpTo(navController.graph.id) {
inclusive = false
}
}
}
//* Exposes raw updates coming directly from the location services
getLocationObserver(locationObserver, lastLocation, currentLatitude, currentLongitude)
//Calling request routes ones to show map box navigation directions in Map Box within the app
StartMapBoxNavigation(
currentLatitude,
currentLongitude,
nvView,
openDialog,
navController, locationObserver
)
// nvView.api.routeReplayEnabled(true) //Starts map navigation by default
nvView.api.startRoutePreview(routes)
if (startRoutePlay) {
nvView.api.startActiveGuidance(routes)
}
}
}
)
}
Expected behavior
We want that after removing the screen from back stack i.e. after leaving the Map Box Screen. Map Box must not take the memory space as captured. It should release the memory after completion of navigation directions and leaving the screen. There should be any method to clear the Navigation View object.
The text was updated successfully, but these errors were encountered:
@testus74966 for Maps SDK, we have a optional compose extension currently in preview that helps to integrate Maps to the app using jetpack compose, and it handles the Map lifecycle properly. Unfortunately there's no NavigationView compose extension integration yet at the moment, would be good to open a ticket in Navigation SDK.
Alternatively, you can reference to our lifecycle implementation to properly handle the lifecycle of your NavigationView, I think the main thing needed is to call mapView.destroy in the DisposableEffect.onDispose.
Environment
Observed behavior and steps to reproduce
I have used Map Box Navigation to show the navigation directions from one location to another location using Android View in Jetpack Compose. After going back to previous screen I also removed the Map Box Screen from back stack of Jetpack Compose. But after removing the screen, memory space captured by Map Box using Android View Jetpack Compose is not freeing up. This is causing my android application to work slow. Please try to fix this issue. Below are the details with Evidences.
https://gi
thub.com/mapbox/mapbox-maps-android/assets/162621406/363d3b88-6d0e-4487-9a78-285232bbd183
Map Box Navigation Code: -
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.os.Build
import androidx.activity.compose.BackHandler
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.app.ActivityCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.NavController
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.google.gson.Gson
import com.mapbox.geojson.Point
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
import com.mapbox.navigation.core.trip.session.LocationObserver
import com.mapbox.navigation.dropin.NavigationView
import com.metropavia.R
import com.metropavia.utils.AppConstants.EMPTY
import com.metropavia.utils.AppConstants.HOSPITAL_ROUTE_SCREEN
import com.metropavia.utils.CommonMethodsUtils.printLog
import com.metropavia.utils.Utils.requestRoutes
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/**
@Date: 07/12/2023
@desc: MapBoxNavigationUI to start the MapBox navigation directions by using Drop In UI of MapBox
*/
@RequiresApi(Build.VERSION_CODES.S)
@composable
fun MapBoxNavigationScreen(
destLat: Double,
destLong: Double,
navController: NavController,
context: Context = LocalContext.current
) {
val openDialog: MutableState = remember { mutableStateOf(EMPTY) }
val isInComposition = remember { mutableStateOf(true) }
val mapBoxNavigation: MutableState<MapboxNavigation?> = remember { mutableStateOf(null) }
val locationObserver: MutableState<LocationObserver?> = remember { mutableStateOf(null) }
val nvView: MutableState<NavigationView?> = remember { mutableStateOf(null) }
val lastLocation: MutableState<Location?> = remember { mutableStateOf(null) }
val currentLatitude: MutableState<Double?> = remember { mutableStateOf(null) }
val currentLongitude: MutableState<Double?> = remember { mutableStateOf(null) }
DisposableEffect(Unit) {
mapBoxNavigation.value = MapboxNavigationApp.current()
onDispose {
mapBoxNavigation.value = null
locationObserver.value = null
lastLocation.value = null
nvView.value = null
currentLatitude.value = null
currentLongitude.value = null
isInComposition.value = false
Runtime.getRuntime().gc()
}
}
printLog("destLaLong", "$destLat, $destLong")
//Compose Lifecycles
ComposableLifecycle { _, event ->
when (event) {
Lifecycle.Event.ON_CREATE -> {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
mapBoxNavigation.value?.startTripSession()
// nvView.value?.api?.routeReplayEnabled(true) //Starts map navigation by default
}
printLog("Compose_Lifecycles", "on create")
}
}
//On Back press unregistering the location observer and clearing the current location values
BackHandler {
locationObserver.value?.let { location ->
mapBoxNavigation.value?.unregisterLocationObserver(location)
}
currentLongitude.value = null
currentLongitude.value = null
navController.navigate(HOSPITAL_ROUTE_SCREEN) {
popUpTo(navController.graph.id) {
inclusive = false
}
}
}
//* Exposes raw updates coming directly from the location services
getLocationObserver(locationObserver, lastLocation, currentLatitude, currentLongitude)
//Calling request routes ones to show map box navigation directions in Map Box within the app
StartMapBoxNavigation(
currentLatitude,
currentLongitude,
nvView,
openDialog,
navController, locationObserver
)
if (isInComposition.value) {
//MabBox with navigation view to show and handle the Map Box UI
AndroidView(
modifier = Modifier
.fillMaxSize(1f),
factory = { myContext ->
NavigationView(
context = myContext,
accessToken = myContext.getString(R.string.mapbox_access_token)
).apply {
nvView.value = this
nvView.value?.customizeViewOptions {
enableMapLongClickIntercept = false
}
}
},
update = {
//Registering location Observer to check the location accurately
locationObserver.value?.let { mapBoxNavigation.value?.registerLocationObserver(it) }
},
onRelease = { navigationView ->
//https://blog.stackademic.com/how-to-use-android-view-inside-jetpack-compose-and-vise-versa-843596485c5d
navigationView.apply {
this.removeAllViews()
Runtime.getRuntime().gc()
}
}
)
}
/* AndroidViewBinding(
modifier = Modifier
.fillMaxSize(1f),
factory = ActivityMapBoxNavigationBinding::inflate
) {
nvView.value = navigationView
nvView.value?.customizeViewOptions {
enableMapLongClickIntercept = false
}
}
/**
@Date: 07/12/2023
@desc: Composable Method to manage the Compose Lifecycles
*/
@composable
fun ComposableLifecycle(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onEvent: (LifecycleOwner, Lifecycle.Event) -> Unit
) {
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { source, event ->
onEvent(source, event)
}
lifecycleOwner.lifecycle.addObserver(observer)
}
}
/**
@Date: 05/01/2024
@desc: Method to get the live location using location observer
*/
fun getLocationObserver(
locationObserver: MutableState<LocationObserver?>,
lastLocation: MutableState<Location?>,
currentLatitude: MutableState<Double?>,
currentLongitude: MutableState<Double?>
): MutableState<LocationObserver?> {
locationObserver.value = object : LocationObserver {
override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
lastLocation.value = locationMatcherResult.enhancedLocation
printLog("last_location", Gson().toJson(lastLocation))
}
}
return locationObserver
}
/**
@Date: 05/01/2024
@desc: Composable function to get the routes and start active navigation directions from the
current location to the destination location
*/
@composable
fun StartMapBoxNavigation(
currentLatitude: MutableState<Double?>,
currentLongitude: MutableState<Double?>,
nvView: MutableState<NavigationView?>,
showMsg: MutableState,
navController: NavController,
locationObserver: MutableState<LocationObserver?>,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val mapBoxNavigation: MutableState<MapboxNavigation?> = remember { mutableStateOf(null) }
val openDialog: MutableState = remember { mutableStateOf(false) }
DisposableEffect(Unit) {
mapBoxNavigation.value = MapboxNavigationApp.current()
onDispose {
mapBoxNavigation.value = null
}
}
//Calling request routes ones to show map box navigation directions in Map Box within the app
LaunchedEffect(key1 = (currentLatitude.value != null) && (currentLongitude.value != null)) {
//Calling find routes to draw route path and get navigation directions
printLog(
"latitude_longitude",
"Start Location:\n Lat: ${currentLatitude.value},\n Long: ${currentLongitude.value}" +
" \n Destination Location:\n Lat: ${newLatitude.value},\n Long: ${newLongitude.value}"
)
currentLongitude.value?.let { long ->
currentLatitude.value?.let { lat ->
printLog(
"latitude_longitude",
"Start Location:\n Lat: ${currentLatitude.value},\n Long: ${currentLongitude.value}" +
" \n Destination Location:\n Lat: ${newLatitude.value},\n Long: ${newLongitude.value}"
)
nvView.value?.let {
scope.launch(dispatcher) {//28.62047897993122, 77.37284099069734
try {
requestRoutes(
mapBoxNavigation.value,
context,
it, //28.55549253851863, 77.55376514865961-Fortis Hospital
Point.fromLngLat(long, lat), //28.944144718191748, 77.33268965606032
Point.fromLngLat( //28.619763217171204, 77.37181101826435
/API lat and long/ client lat and long/ Noida lat and long/
newLongitude.value.toDouble() /77.37181101826435/,
newLatitude.value.toDouble()/28.619763217171204/
),
showMsg, true
)
} catch (ex: Exception) {
Firebase.crashlytics.recordException(ex)
}
}
}
}
}
}
//Showing Alert Message if the route is not available for longer distances
if (showMsg.value.isNotEmpty()) {
openDialog.value = true
ShowAlertDialog(
openDialog = openDialog,
title = showMsg.value
) {
scope.launch(dispatcher) {
locationObserver.value?.let { location ->
mapBoxNavigation.value?.unregisterLocationObserver(location)
}
mapBoxNavigation.value?.stopTripSession()
currentLatitude.value = null
currentLongitude.value = null
}
scope.launch {
navController.navigate(HOSPITAL_ROUTE_SCREEN) {
popUpTo(navController.graph.id) {
inclusive = true
}
}
}
}
}
}
/**
Method to request the routes to start an active map box navigation directions for ER Requests (07/12/2023)
@param context
@param nvView Map box Navigation view required to render the route path in Map
@param origin Origin Point for the path to be drawn on Map
@param destination Destination Point for the path to be drawn on Map
@param openDialog Boolean value to open dialog if the path exceeds the maximum path distance or no path is available
@param startRoutePlay Boolean value to start the Navigation Guidance for the patient
*/
fun requestRoutes(
mapBoxNavigation : MapboxNavigation?,
context: Context,
nvView: NavigationView,
origin: Point,
destination: Point,
openDialog: MutableState,
startRoutePlay: Boolean
) {
mapBoxNavigation?.requestRoutes(
routeOptions = RouteOptions
.builder()
.applyDefaultNavigationOptions()
.applyLanguageAndVoiceUnitOptions(context)
.coordinatesList(listOf(origin, destination))
.alternatives(true)
.build(),
callback = object : NavigationRouterCallback {
override fun onCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) {
printLog(
"onCancelled",
"${Gson().toJson(routeOptions)}, ${Gson().toJson(routerOrigin)}"
)
}
// showToast(reasons[0].message, context)
}
// nvView.api.routeReplayEnabled(true) //Starts map navigation by default
nvView.api.startRoutePreview(routes)
if (startRoutePlay) {
nvView.api.startActiveGuidance(routes)
}
}
}
)
}
Expected behavior
We want that after removing the screen from back stack i.e. after leaving the Map Box Screen. Map Box must not take the memory space as captured. It should release the memory after completion of navigation directions and leaving the screen. There should be any method to clear the Navigation View object.
The text was updated successfully, but these errors were encountered: