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:
- Using
rememberSaveable
. It may not be the best solution in this case, because you have to serialize your object in some way. - 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 passcontext
intostart
, 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)
}