Reputation: 1
I'm trying to display a dialog as soon as the current view renders. However, the dialog is shown twice (maybe due to how recomposition works) and requires two dismisses to disappear.
val showGpsDisabledDialog by viewModel.showGpsDisabledDialog.collectAsState()
Log.d(TAG, showGpsDisabledDialog.toString())
if (showGpsDisabledDialog) {
Log.d(TAG, "dialog")
val context = LocalContext.current
BasicAlertDialog(
onDismissRequest = {
Log.d(TAG, "dismiss")
viewModel.closeGpsDisabledDialog()
},
) {
Text("dialog")
}
}
ViewModel:
@HiltViewModel
class MapViewModel @Inject constructor(): ViewModel() {
private var _showGpsDisabledDialog: MutableStateFlow<Boolean> = MutableStateFlow(true)
val showGpsDisabledDialog: StateFlow<Boolean> get() = _showGpsDisabledDialog.asStateFlow()
fun openGpsDisabledDialog() {
if(!_showGpsDisabledDialog.value)
_showGpsDisabledDialog.value = true
}
fun closeGpsDisabledDialog() {
if(_showGpsDisabledDialog.value)
_showGpsDisabledDialog.value = false
}
}
I tried to store the state in the composable, then in viewmodel. I tried using LaunchedEffect to set those flags (so they are not true before the composable renders) I tried setting them to true in viewmodel using a delay.
I've figured out that the dialog is displayed twice in all these cases by rendering a simple dialog with a text and noticed that there are two text overlapping
Upvotes: 0
Views: 71
Reputation: 2513
The answer to your question is pretty simple.
Use 2 variables:
ViewModel
Dialog
is being displayed or not.Changes should look something like this.
// use this variable to fetch data from backend or viewmodel
val showGpsDisabledDialog by viewModel.showGpsDisabledDialog.collectAsState()
// use this variable to store whether the dialog is being displayed or not.
var isDialogOpen by remember { mutableStateOf(false) }
Log.d(TAG, showGpsDisabledDialog.toString())
// Show dialog when both the conditions are satisfied.
if (showGpsDisabledDialog && isDialogOpen) {
Log.d(TAG, "dialog")
val context = LocalContext.current
BasicAlertDialog(
onDismissRequest = {
Log.d(TAG, "dismiss")
isDialogOpen = false
viewModel.closeGpsDisabledDialog()
},
) {
Text("dialog")
}
}
Upvotes: 0
Reputation: 865
Your code is very complicated and hard to understand. From your above code, I think you want to show a dialog if the device's GPS is disabled.
Here is my simple and reusable approach:
Context extension: You can write the following extension function (good to have it in a separate file under the utils package in your project). It returns a callback flow streaming the real-time changes in the GPS settings.
fun Context.observeGpsStatus(): Flow<Boolean> = callbackFlow {
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGpsEnabled = {
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}
// Emit initial status
trySend(isGpsEnabled())
val gpsStatusReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
trySend(isGpsEnabled())
}
}
val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
registerReceiver(gpsStatusReceiver, filter)
awaitClose {
unregisterReceiver(gpsStatusReceiver)
}
}
GpsStatusObserver composable: This is a simple reusable composable which can be integrated in any screen in any project.
@Composable
fun GpsStatusObserver() {
val context = LocalContext.current
val gpsFlow = remember { context.observeGpsStatus() }
val isGpsEnabled = gpsFlow.collectAsState(initial = true)
val showDialog = remember { mutableStateOf(true) }
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {}
if (!isGpsEnabled.value && showDialog.value) {
TwoActionsDialog(
onDismissRequest = {
Log.d(TAG, "dismiss")
showDialog.value = false
},
mainText = "Gps disabled",
secondaryText = "Please enable location services.\nOtherwise, the app will not know where you took the photos.",
primaryButtonText = "OK, go to settings",
secondaryButtonText = "No, continue without location",
onPrimaryButtonClick = {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
launcher.launch(intent)
},
onSecondaryButtonClick = {
Log.d(TAG, "decline")
showDialog.value = false
}
)
}
}
Then simply call it in your screen composable:
@Composable
fun ExampleScreen() {
GpsStatusObserver()
// other content of the screen
}
Upvotes: 0