Home > Software design >  When I use the double value it loses the value and prints number 0.0 using collectAsState
When I use the double value it loses the value and prints number 0.0 using collectAsState

Time:03-23

My class that takes the location:

class LocationUtil(context: Context) {

    private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
    private var locationListener: LocationListener? = null

    var locationStateFlow = MutableStateFlow(Location(LocationManager.GPS_PROVIDER))
    val gpsProviderState = mutableStateOf(false)
    val isStart: MutableState<Boolean> = mutableStateOf(false)

    private val locHandlerThread = HandlerThread("LocationUtil Thread")

    init {
        locHandlerThread.start()
    }

    @SuppressLint("MissingPermission")
    fun start(minTimeMs: Long = min_time, minDistanceM: Float = min_distance) {

        locationListener().let {
            locationListener = it

            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, it, locHandlerThread.looper)

        }
        gpsProviderState.value = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
        isStart.value = true
    }

    @SuppressLint("MissingPermission")
    fun stop() {
        locationListener?.let {
            locationManager.removeUpdates(it)
        }
        isStart.value = false
    }


    private fun locationListener() = object : LocationListener {
        override fun onLocationChanged(location: Location) {

            locationStateFlow.value = location
            Log.i("GPS",locationStateFlow.value.longitude.toString() ">>" locationStateFlow.value.latitude.toString())
        }

        @Deprecated("Deprecated in Java")
        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
        }

        override fun onProviderEnabled(provider: String) {
            gpsProviderState.value = true
        }

        override fun onProviderDisabled(provider: String) {
            gpsProviderState.value = false
        }
    }


    companion object {
        const val min_time: Long = 0L
        const val min_distance: Float = 0f
    }

}

My composable method which takes the value of the variable var locationStateFlow = MutableStateFlow(Location(LocationManager.GPS_PROVIDER)):

@Composable
private fun FeatureThatRequiresCameraPermission(multiplePermissionsState: MultiplePermissionsState) {
    if (multiplePermissionsState.allPermissionsGranted) {
        // If all permissions are granted, then show screen with the feature enabled
        val context = LocalContext.current
        val locationUtil = LocationUtil(context)
        locationUtil.start()
        val gpsEnable by locationUtil.gpsProviderState
        val coordenadas by locationUtil.locationStateFlow.collectAsState(
                Location(
                        LocationManager.GPS_PROVIDER)
        )

        Text(text = coordenadas.latitude.toString())

    } else {
        Column {
            Spacer(modifier = Modifier.height(8.dp))
            Button(onClick = { multiplePermissionsState.launchMultiplePermissionRequest() }) {
                Text("Request permissions")
            }
        }
    }
}

My Text(text = coordinates.latitude.toString()) returns 0.0 But my coordinate method returns correctly:

 override fun onLocationChanged(location: Location) {

            locationStateFlow.value = location
            Log.i("GPS",locationStateFlow.value.longitude.toString() ">>" locationStateFlow.value.latitude.toString())
        }

Does anyone have any idea why I can't get the value correctly?

CodePudding user response:

Each time your flow emits a new value, collectAsState triggers recomposition. It means, that whole FeatureThatRequiresCameraPermission is called again.

On each recomposition you're creating a new LocationUtil, which has zero initial coordinates. Also you're calling start() each time.

val locationUtil = LocationUtil(context)
locationUtil.start()

The simplest solution to save some object between recompositions is to use remember, for example, like this:

val locationUtil = remember {
    LocationUtil(context)
        .apply {
            start()
        }
}

But note, that it's still gonna be reset during configuration change, e.g. screen rotation.

To save it during configuration change, you have two options:

  1. Using rememberSaveable. It may not be the best solution in this case, because you have to serialize your object in some way.
  2. Converting your LocationUtil to view model - it's lifecycle is tight to your activity/fragment or a navigation route(if your're using Compose Navigation). You can pass context into start, it can look something like this:
class LocationViewModel: ViewModel() {

    private lateinit var locationManager: LocationManager
    private var locationListener: LocationListener? = null

    var locationStateFlow = MutableStateFlow(Location(LocationManager.GPS_PROVIDER))
    var gpsProviderState by mutableStateOf(false)
        private set
    var isStart by mutableStateOf(false)
        private set

    private val locHandlerThread = HandlerThread("LocationUtil Thread")

    init {
        locHandlerThread.start()
    }

    fun start(context: Context, minTimeMs: Long = min_time, minDistanceM: Float = min_distance) {
        // LaunchedEffect is gonna be re-launched during configuration change
        // we don't need to re-start if it's running
        if (isStart) return
        if (!this::locationManager.isInitialized) {
            locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
        }
        locationListener().let {
            locationListener = it

            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, it, locHandlerThread.looper)
        }
        gpsProviderState = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
        isStart = true
    }

    fun stop() {
        locationListener?.let {
            locationManager.removeUpdates(it)
        }
        isStart = false
    }


    private fun locationListener() = object : LocationListener {
        override fun onLocationChanged(location: Location) {

            locationStateFlow.value = location
            Log.i("GPS",locationStateFlow.value.longitude.toString() ">>" locationStateFlow.value.latitude.toString())
        }

        @Deprecated("Deprecated in Java")
        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
        }

        override fun onProviderEnabled(provider: String) {
            gpsProviderState = true
        }

        override fun onProviderDisabled(provider: String) {
            gpsProviderState = false
        }
    }


    companion object {
        const val min_time: Long = 0L
        const val min_distance: Float = 0f
    }
}

Usage:

val locationViewModel = viewModel<LocationViewModel>()
val context = LocalContext.current
LaunchedEffect(Unit) {
    locationViewModel.start(context)
}
  • Related