I am trying to get the User Location in Compose.
I found this link and tried the Mitch solution. But it has an infinite loop and the snackbar is not shown. From other links I read that it needs to be attached to a scaffold but it seems than on M3 is no longer valid.
I change the infinite loop from:
LaunchedEffect(true) {
while (true) {
isGPSEnabled = isGPSEnabled(context)
delay(2.seconds)
}
}
To:
LaunchedEffect(true) {
while (!isGPSEnabled) { //while (true) {
isGPSEnabled = isGPSEnabled(context)
delay(2.seconds)
}
}
But I don't know how to make the snackbar to show:
DisposableEffect(
isGPSEnabled,
permissions.shouldShowRationale,
permissions.allPermissionsGranted,
) {
if (!permissions.allPermissionsGranted || permissions.shouldShowRationale) {
scope.launch {
val result = snackbarHostState.showSnackbar(
"Missing required permissions",
"Grant",
withDismissAction = true,
duration = SnackbarDuration.Indefinite,
)
when (result) {
SnackbarResult.Dismissed -> {
}
SnackbarResult.ActionPerformed -> {
permissions.launchMultiplePermissionRequest()
}
}
}
return@DisposableEffect SimulatedDisposableEffectResult
}
I know the code [val result = snackbarHostState.showSnackbar(...) ]is executed because I have a break point in debug that reach it. It stays there an never reach the [when(result)] since it has[duration = SnackbarDuration.Indefinite]
On my Screen I have the following to call the function:
Image(painter = painterResource(R.drawable.baseline_my_location_24),
contentDescription = "Get Coordinates",
modifier = Modifier
.clickable {
doGet = true
}
.size(40.dp)
)
if (doGet) {
ExampleForegroundLocationTrackerScreen()
}
That is for testing purpose, but I really want to receive the value(latitud, longitud) to show them on the screen and later save then in a Database.
After many days or weeks I end up using a BottomSheetScaffold.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LocationScreen(
startLocationUpdates: ()->Unit,
context: Context,
currentLocation: LatLng
) {
////// This permissions are use down bellow at a Button Click event that
////// actually calls for the GPS location. The Button is part of the BottomSheet content.
val permissions = arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
)
////// startActivityForResult function is deprecated and in Jetpack Compose the solution is
////// to use "rememberLauncherForActivityResult" with "ActivityResultContracts"
val launchMultiplePermissions = rememberLauncherForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissionMaps ->
val areGranted = permissionMaps.values.reduce { acc, next -> acc && next }
if (areGranted) {
locationRequired = true
startLocationUpdates()
Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show()
}
}
var readable by remember { mutableStateOf("") }
//val scope = rememberCoroutineScope()
val scaffoldState = rememberBottomSheetScaffoldState()
BottomSheetScaffold(
//modifier = Modifier.heightIn(min=0.dp, max=400.dp),
scaffoldState = scaffoldState,
sheetPeekHeight = 590.dp,
sheetSwipeEnabled = false,
sheetContent = {
Column(
Modifier
.fillMaxWidth()
.padding(6.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (currentLocation.latitude != 0.toDouble() && currentLocation.longitude != 0.toDouble() && readable != "") {
WebViewScreen(location = currentLocation) //, address = readable)
showdialog.value = false
} else {Text(text = "Please click [Get your location]")}
}
}) { innerPadding ->
Box(Modifier.padding(innerPadding)) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Your location: ${currentLocation.latitude}/${currentLocation.longitude}")
if (currentLocation.latitude != 0.toDouble() && currentLocation.longitude != 0.toDouble()) {
Text(
text = "Readable location: $currentLocation"
)
location = ""
readable = getReadableLocation(currentLocation.latitude, currentLocation.longitude, context)
glocation = currentLocation
}
//////Here is where the permissions are used to call for the system permissions pop up.
Button(onClick = {
if (permissions.all {
ContextCompat.checkSelfPermission(
context,
it
) == PackageManager.PERMISSION_GRANTED
}) {
//Get Locations
////// Here once the permissions are granted I call my function that actually gets the GPS Location
startLocationUpdates()
} else {
launchMultiplePermissions.launch(permissions)
}
=
}) {
Text(text = "Get your location")
}
}
}
}
}
Maybe you can read this post for better explanation. In there the example is directed toward Pictures instead of GPS location.
The GPS Location function is the following:
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
override fun onResume() {
super.onResume()
if (locationRequired)
startLocationUpdates()
}
override fun onPause() {
super.onPause()
if (this::locationCallback.isInitialized) {
locationCallback?.let {
fusedLocationClient?.removeLocationUpdates(it)
}
}
}
@SuppressLint("MissingPermission")
private fun startLocationUpdates() {
locationCallback?.let {
val locationRequest = LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY, 100
)
.setWaitForAccurateLocation(false)
.setMinUpdateIntervalMillis(3000)
.setMaxUpdateDelayMillis(100)
.build()
fusedLocationClient?.requestLocationUpdates(
locationRequest,
it,
Looper.getMainLooper()
)
}
}
This code is written in the MainActivity before the onCreate and is pass to the Screen as a Lambda function along the currentLocation:LatLng which is calculated/retrieved by the "startLocationUpdates" function already detail above.
Then inside the onCreate the following code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
setContent {
var currentLocation by remember {
mutableStateOf(LatLng(0.toDouble(), 0.toDouble()))
}
locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult) {
super.onLocationResult(p0)
for (location in p0.locations) {
currentLocation = LatLng(location.latitude, location.longitude)
}
}
}
MainTheme { ...
NavHost(navController = navController, startDestination = "MainScreen") {
composable("LocationScreen") {
LocationScreen(
{ startLocationUpdates() },
this@MainActivity,
currentLocation
)
}
...