I'm using timepickerstate and datepickerstate for ...
# compose-android
a
I'm using timepickerstate and datepickerstate for TimePicker and datepicker. I'm initializing them with default values (current time and current date). I'm then updating time and date in the ViewModel. In my composable, after they've been updated in the ViewModel, the picker dialogs don't reflect the new values, but are set to the initial values despite using
rememberTimePicker
and
rememberDatePicker
, respectively. To make date reflect new chosen date by user, I've to manually do a LaunchedEffect on the date (as stored in the ViewModel) and update the DatePickerState accordingly. However, TimePickerState does not have a way to update the time. Has anyone else ran into this and if so, how did you work around it?
c
before opening the dialog just create a new “picker state” with those values. the “original initial” value should come from the same source in the viewmodel anyway.
so actually the source of truth of the initial value of the picker state is the view model. build your logic around this requirement and you are fine.
a
If I update the ViewModel values, when I open the TimePicker dialog, I see the old (system default) time.
c
some code snipets will help to see your “architecture”. but basically when using the code snippet from yesterday, you just pass the
initalValue
to your “wrapper” composable
Copy code
@Composable
fun TimeDailer(
    onConfirm: (TimePickerState) -> Unit,
    onDismiss: () -> Unit,
    modifier: Modifier = Modifier,
    initialTime: LocalTime =
        Clock.System
            .now()
            .toLocalDateTime(TimeZone.currentSystemDefault())
            .time,
) {
    val timePickerState =
        rememberTimePickerState(
            initialHour = initialTime.hour,
            initialMinute = initialTime.minute,
            is24Hour = true,
        )

    var hasError by remember {
        mutableStateOf(false)
    }
and then when where you show the `TimeDailer`:
a
I do initialize the timepickerstate with initial hour and minute. However, when I change the ViewModel values, the timepickerstate won't update (it remains with old values).
c
in the view model the inital value need sot be a
MutableState
or something like this that you then “observe” in the composable to get the updated values. check the code lab: https://developer.android.com/codelabs/basic-android-kotlin-compose-viewmodel-and-state#5
a
Yep. They're mutableSetOf()
c
some code snippets will help to see your “architecture”.
a
It's something like this:
Copy code
// ViewModel
data class TimeClass(
    val hour: Int = Calendar.getInstance().get(Calendar.HOUR_OF_DAY),
    val minute: Int = Calendar.getInstance().get(Calendar.MINUTE)
)

class MyViewModel : ViewModel() {
    private val _object = mutableStateOf(Object())  // updated asynchronously before navigating to composable screen
    val object = _object

    private val _selectedTime = mutableSetOf(TimeClass())
    val selectedTime = _selectedTime

    fun updateObject(object: Object) {
        _object.value = object
    }

    fun updateTime(hour: Int, minute: Int) {
        val calendar = Calendar.getInstance().apply {
            set(Calendar.HOUR_OF_DAY, hour)
            set(Calendar.MINUTE, minute)
        }
        _selectedTime.value = TimeClass(
            calendar.get(Calendar.HOUR_OF_DAY),
            calendar.get(Calendar.MINUTE)
        )
}
Then, in my composable:
Copy code
@Composable
fun MyComposable(modifier: Modifier = Modifier, myViewModel: MyViewModel) {
    val object = myViewModel.object.value

    LaunchedEffect(object) {
        if (object.time != null) {
            myViewModel.updateTime(object.time.hour, object.time.minute)
        }
    }

    DateTimePicker(modifier = modifier, myViewModel = myViewModel)
}
Copy code
@Composable
fun DateTimePicker(modifier..., myViewModel: MyViewModel) {
    val time = myViewModel.selectedTime.value
    val timePickerState = rememberTimePickerState(
        initialHour = time.hour,
        initialMinute = time.minute
    )
    TimePickerDialog(...) {
        TimePicker(state = timePickerState)
    }
}
Where
TimePickerDialog
is of the nature of https://developer.android.com/develop/ui/compose/components/time-pickers-dialogs#advanced Outside of these.composables, there's an asynchronous update to
object
that's stored in myViewModel. The idea is: when this object is updated, the time is updated which should be reflected in the
rememberTimePickerState
. But it's not. TimePicker remains with default (system) values.
c
I’d say you have a race condition
Copy code
LaunchedEffect(object) {
        if (object.time != null) {
            myViewModel.updateTime(object.time.hour, object.time.minute)
        }
    }
will update the time in the view model but at that time your dialog is already visible with the old value and you do not receive the updated value in the
DateTimePicker
. Try logging the
time
in the
DateTimePicker
.
Copy code
LaunchedEffect(time) {
   Log.v("DatePicker", time.toString())
}
I guess you’ll see the time updated in the logs.
a
Shouldn't
rememberTimePickerState
recompose with the new values since the ViewModel stores the parameter as
mutableStateOf()
?
And how do I make sure I navigate to the screen only when the asynchronous calls are done? This would make the app synchronous in this respect.
c
Shouldn’t
rememberTimePickerState
recompose with the
as its “remembering” it does not because you cannot provide a key.
And how do I make sure I navigate to the screen only when the asynchronous calls are done?
create a loading state and open the screen after “loading” is complete.
also, why is a dialog a “screen”?
a
Don't you need default values for the loading state anyways?
It's code snippet. Is not the entire code.
c
2 states - loading and comlete 😉
and initial is “loading”
And how do I make sure I navigate to the screen
you refer to it as screen.
so the condition will be
Copy code
if (loadingState == complete){
DateTimePicker(...)
}
a
You mean to use a when on the UiState and render the UI with the screen having the updated values and a CPI (for example) when it's loading?
c
yes
not sure waht you mean with
CPI
though.
a
Wouldn't it be easier for TimePickerState to simply allow mutation just like DatePickerState does?
CPI = Circularprogressindicator
😅 1
c
sure, I guess you can file a feature request.
a
Where can I do that (I'm new to jetpack compose). Do you have a link to the tracker?
I'll leave the
when(UiState)
for another iteration in the future if the feature request doesn't get done by then (app is not in production)
a
238 Views