I'm currently trying to implement DatePickerDialog
and TimePickerDialog
, and found out that it's quite verbose to get their values. Like you have to preemptively declare a couple of variables that have to be set on onDateSet
and onTimeSet
. I thought that it would be nice if I could create a easily reusable function that would return what values a picker dialog's have. But I can't figure out what needs to be done. I tested using coroutine related stuff like creating a suspend functions for the picker dialog's that looks like this:
suspend fun showDatePickerDlg(yr: Int, mth: Int, day: Int): LocalDateTime{
val res = suspendCoroutine<LocalDateTime> { cont ->
val datePickerDialog = DatePickerDialog(requireActivity(),
{ _, year, monthOfYear, dayOfMonth ->//onDateSet
cont.resume(LocalDateTime.of(year, monthOfYear, dayOfMonth, 0, 0))
}, yr, mth, day
)
requireActivity().runOnUiThread{
datePickerDialog.show()
}
}
return res
}
This code is inside a fragment btw.
And gets launched like:
GlobalScope.async {
val schedDate = getDatePickerDlg(year, month, day)
val schedTime = getTimePickerDlg(hour, minute)
Log.i("TAG", "date: $schedDate")
Log.i("TAG", "time: $schedTime")
}
I might be doing something wrong because the dialogs aren't showing even with runOnUiThread
, which I think was necessary because anything inside a suspend are technically not on the main/ui thread.
But my main question is still is what the title says.
The recommended way to do it in the documentation is much more verbose than what you seemed to be doing before creating your suspend function! This is because they recommend that you host your Dialog inside a DialogFragment (so it will be preserved under configuration changes or when the app is suspended and comes back).
If you don't mind the dialog disappearing when the screen rotates, you can skip that, but you are still left with having to use the listener.
I'm not exactly sure what you are describing when you say you have to pre-emptively declare variables. You still have to use the variable inside the listener, because the code outside the listener is called first. So really, your flow should look like this:
DatePickerDialog(requireActivity(), { _, pickedYear, pickedMonth, pickedDay ->
val schedDate = LocalDateTime.of(pickedYear, pickedMonth + 1, pickedDay)
TimePickerDialog(requireActivity(), { _, pickedHour, pickedMinute ->
val schedTime = LocalTime.of(pickedHour, pickedMinute)
// Use schedDate and schedTime in here....
// ...
// ...
}, initialHour, initialMinute, false).show()
}, initialYear, initialMonth, initialDay).show()
So yes, this nested callback looks horrible, and could be improved with suspend functions. But remember, this is not compatible with DialogFragments.
Here are some problems with your coroutine solution:
suspendCoroutine
instead of suspendCancellableCoroutine
, so if your coroutine is cancelled, the stuff inside the lambda is leaked.GlobalScope
instead of lifecycleScope
, so your coroutines leaks the Activity and the Fragment.async
instead of launch
even though your coroutine isn't intended to return anything.Here is how I would define the suspend function to handle cancellation and be called on the proper thread. I would also make it a Context extension function so you can reuse it in any activity or fragment.
suspend fun Context.getDateFromUser(initialDate: LocalDate = LocalDate.now()): LocalDate? = withContext(Dispatchers.Main) {
suspendCancellableCoroutine { cont ->
val onCancel = DialogInterface.OnCancelListener {
cont.resume(null)
}
val onSet = DatePickerDialog.OnDateSetListener { _, year, month, day ->
cont.resume(LocalDate.of(year, month + 1, day))
}
val dialog = DatePickerDialog(this@getDateFromUser, onSet, initialDate.year, initialDate.month -1, initialDate.dayOfMonth).apply {
onCancelListener = onCancel
show()
}
cont.invokeOnCancellation { dialog.cancel() }
}
}
In a fragment you might use it like this:
viewLifecycleOwner.lifecycleScope.launch {
val schedDate = requireActivity().getDateFromUser() ?: someDefaultLocalDate
val schedTime = requireActivity().getTimeFromUser() ?: someDefaultLocalTime
// Use schedDate and schedTime in here....
// ...
// ...
}