I need to collect location in various parts of the app and thus, decided to create a reusable component. The issue is after a fresh installation of the app, it asks for for permission but after granted, the GoogleMap doesn't populate. But if i close the app totally and restart it, it remembers the permission and populates the map.
import android.Manifest
import android.location.Geocoder
import android.location.LocationManager
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.AlertDialog
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.TextFieldDefaults.indicatorLine
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.location.LocationManagerCompat
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.libraries.places.api.Places
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MapProperties
import com.google.maps.android.compose.MapUiSettings
import com.google.maps.android.compose.rememberCameraPositionState
import kotlinx.coroutines.launch
@OptIn(
ExperimentalMaterialApi::class,
ExperimentalPermissionsApi::class,
)
@Composable
fun LocationPicker(
viewModel: LocationViewModel = hiltViewModel(),
onSelect: (value: GPSCoordinates) -> Unit,
) {
val context = LocalContext.current
viewModel.fusedLocationClient =
LocationServices.getFusedLocationProviderClient(context)
Places.initialize(LocalContext.current, GMAP_API_KEY)
viewModel.placesClient = Places.createClient(context)
viewModel.geoCoder = Geocoder(context)
val locationPermissionState =
rememberMultiplePermissionsState(
listOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
),
)
LaunchedEffect(locationPermissionState) {
// Check if permissions are already granted
if (locationPermissionState.allPermissionsGranted) {
viewModel.locationState =
LocationState.LocationAvailable(
GPSCoordinates(
latitude = 0.0,
longitude = 0.0,
accuracy = 0f,
altitutde = 0.0,
),
)
viewModel.getCurrentLocation()
} else {
// Permissions are not granted, request them
locationPermissionState.launchMultiplePermissionRequest()
}
}
Column(
modifier =
Modifier
.padding(horizontal = Dimensions.Large),
) {
AnimatedContent(
viewModel.locationState,
) { state ->
when (state) {
is LocationState.LocationDisabled -> {
RequestEnableLocationDialog {
if (it) {
viewModel.getCurrentLocation()
}
}
}
is LocationState.LocationLoading -> {
Row(
modifier =
Modifier
.fillMaxWidth()
.height(LocalConfiguration.current.screenHeightDp.dp / 3),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
CircularProgressIndicator(modifier = Modifier.testTag("Progress Indicator"))
Text(
modifier = Modifier.padding(horizontal = Dimensions.Small),
text = stringResource(id = R.string.preparing_map),
)
}
}
is LocationState.Error -> {
Text(text="error")
}
is LocationState.LocationAvailable -> {
val cameraPositionState =
rememberCameraPositionState {
position =
CameraPosition.fromLatLngZoom(
LatLng(
state.gpsCoordinates.latitude ?: 0.0,
state.gpsCoordinates.longitude ?: 0.0,
),
15f,
)
}
val mapUiSettings by remember { mutableStateOf(MapUiSettings()) }
val mapProperties by remember {
mutableStateOf(
MapProperties(
isMyLocationEnabled = true,
),
)
}
val scope = rememberCoroutineScope()
LaunchedEffect(viewModel.currentGPSLocation) {
cameraPositionState.animate(
CameraUpdateFactory.newLatLng(
LatLng(viewModel.currentGPSLocation.latitude ?: 0.0, viewModel.currentGPSLocation.longitude ?: 0.0),
),
)
}
LaunchedEffect(cameraPositionState.isMoving) {
if (!cameraPositionState.isMoving) {
viewModel.getAddress(cameraPositionState.position.target)
}
}
Box(
modifier =
Modifier
.fillMaxWidth()
.height(LocalConfiguration.current.screenHeightDp.dp / 3),
) {
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState,
uiSettings = mapUiSettings,
properties = mapProperties,
onMapClick = {
scope.launch {
cameraPositionState.animate(CameraUpdateFactory.newLatLng(it))
onSelect(
GPSCoordinates(
latitude = it.latitude,
longitude = it.longitude,
accuracy = state.gpsCoordinates.accuracy,
altitutde = state.gpsCoordinates.altitutde,
),
)
}
},
)
Icon(
painter = painterResource(id = R.drawable.baseline_location_on_24),
tint = MaterialTheme.colors.error,
contentDescription = null,
modifier =
Modifier
.size(48.dp)
.align(Alignment.Center),
)
}
}
}
}
TextField(
modifier =
Modifier
.fillMaxWidth()
.padding(vertical = Dimensions.XLarge)
.indicatorLine(
enabled = true,
isError = false,
interactionSource = remember { MutableInteractionSource() },
colors =
TextFieldDefaults.textFieldColors(
unfocusedIndicatorColor = MaterialTheme.colors.onBackground,
),
focusedIndicatorLineThickness = 2.dp,
unfocusedIndicatorLineThickness = 2.dp,
),
value = viewModel.locationAddress,
onValueChange = {
viewModel.locationAddress = it
viewModel.searchPlaces(it)
},
label = {
Text(text = stringResource(id = R.string.enter_client_address))
},
colors =
TextFieldDefaults.textFieldColors(
backgroundColor = MaterialTheme.colors.background,
unfocusedIndicatorColor = MaterialTheme.colors.primary,
),
)
Box(
modifier =
Modifier
.fillMaxWidth()
.absoluteOffset(y = -((LocalConfiguration.current.screenHeightDp.dp / 2) + Dimensions.Large))
.background(MaterialTheme.colors.background),
) {
Column(
modifier = Modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
AnimatedVisibility(
viewModel.locationAutofill.isNotEmpty(),
modifier =
Modifier
.fillMaxWidth(),
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp),
reverseLayout = true,
) {
items(viewModel.locationAutofill) {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable {
viewModel.locationAddress = it.address
viewModel.locationAutofill.clear()
viewModel.getCoordinates(it)
},
) {
Text(it.address)
}
}
}
}
}
}
}
}
private fun locationEnabled(context: Context): Boolean {
val locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return LocationManagerCompat.isLocationEnabled(locationManager)
}
@Composable
private fun RequestEnableLocationDialog(onRequestPermissionResult: (Boolean) -> Unit) {
val context = LocalContext.current
val openSettingsLauncher =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
) {
onRequestPermissionResult(
locationEnabled(context),
)
}
AlertDialog(
onDismissRequest = { onRequestPermissionResult(false) },
title = { Text(text = stringResource(id = R.string.enable_location)) },
text = { Text(text = stringResource(id = R.string.enable_location_description)) },
confirmButton = {
TextButton(
onClick = {
openSettingsLauncher.launch(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
},
) {
Text(text = stringResource(id = R.string.open_settings))
}
},
dismissButton = {
TextButton(
onClick = { onRequestPermissionResult(false) },
) {
Text(text = stringResource(id = R.string.cancel))
}
},
)
}