Issue
I am showing a dialog based on the showPermissionDialogState
bool whose initial value is true i.e show dialog first-time app open.
In Preference class
override val showPermissionDialog = _dataStore.data.map {
it[PREF_SHOW_PERM_DIALOG] ?: DEF_SHOW_PREM_DIALOG
}
In UseCase
override fun execute(parameters: Unit): Flow<Result<Boolean>> =
_preferenceStorage.showPermissionDialog.map {
Result.Success(it)
}
Initially DEF_SHOW_PREM_DIALOG = true In ViewModel
val showPermissionDialogState = showPermissionDialog(Unit).map {
it.successOr(DEF_SHOW_PREM_DIALOG)
}.stateIn(viewModelScope, WhileViewSubscribed, DEF_SHOW_PREM_DIALOG)
After the dialog is shown I am saving the false in dataStore
but when I open the app again the dialog shows and suddenly disappears i.e first getting the initial value than the original one.
In composable screen
val canShowPermissionDialogState by viewModel.showPermissionDialogState.collectAsStateWithLifecycle()
var openPermissionDialogState by rememberSaveable { mutableStateOf(canShowPermissionDialogState) }
LaunchedEffect(canShowPermissionDialogState) {
// Do not show battery optimization dialog if battery optimization is disabled for this app.
if (viewModel.isBatteryOptimizationPermissionGranted) {
viewModel.onHomeEvent(HomeEvent.NeverShowPermissionDialog)
}
openPermissionDialogState = canShowPermissionDialogState
}
if (openPermissionDialogState) {
PermissionMessageDialog(
title = stringResource(R.string.battery_opti_perm_title),
message = stringResource(R.string.battery_opti_perm_msg)
.withBold(boldText = stringResource(R.string.battery_opti_perm_guide)),
onDialogDismiss = { permanently ->
if (permanently) {
viewModel.onHomeEvent(HomeEvent.NeverShowPermissionDialog)
}
openPermissionDialogState = false
}
) {
openAppSettingPage(context)
}
}
Solution
Initialise your state-holder like so,
var showPermissionDialogState by Delegates.notNull<Boolean>()
Then create an initialisation method inside the viewModel to initialise this variable in,
fun initPermState() {...} // Add thou, thy logic
BEFORE you display this variable anywhere (by accessing it in an @Composable
), make sure to call this initialisation method.
Like, call it right after you initialise your viewModel in your main activity onCreate
.
You yourself stated the diagnosis that the issue was that the default value was being shown then the actual values means the default value was being READ before the actual value. Since the same variable is responsible for displaying the value, it means the variable was being READ before it was UPDATED. HENCE, all you had to do was to make sure you update the variable BEFORE reading it. That's what we've done here.
Uh, the fact that the dialog just flashes could also be indicative of asynchronous update of the variable by the proto reader. The update is TRIGGERED before the access but actually ends up updating after the fact, because of its non-blocking nature. Solution for that, as easy as it gets is to set the default value to false
, instead of true, so the first card might be a bit delayed but it won't make for an unpleasant ux by flashing every time the user opens your app again.
Answered By - Richard Onslow Roper
Answer Checked By - Marie Seifert (JavaFixing Admin)