Recently, I started working on a new Android project using Jetpack compose. I wanted to have two datetime field to capture start datetime and end datetime using the inbuilt pickers. The date and time selected from the picker should populate single text field with date and time together or two separate text fields representing the date and time respectively.

Much to my surprise there are no datetime picker composable as of now. Furthermore, I realized that  DatePickerDialog and DatePicker exists but managing populating the text field with DatePicker state needs to be done by custom code. Finally, for time, TimePicker exists but there is no out of the box TimePickerDialog as of now. In the post, I am sharing the custom datetime composable that I created to accomplish my requirements.

DateTimePicker Preview

The below code when added inside a column will render the two datetime fields.

       CustomDateTimePicker(
            title = "Start datetime",
            state = startDateTimeState
        )
        CustomDateTimePicker(
            title = "End datetime",
            state = endDateTimeState
        )

The complete running code can be downloaded from the github repository.

Github Repo: MyDateTimePicker App

High-level design

Below are the different components written to create and display the datetime composable.

DateTimePicker.kt

The method CustomDateTimePicker is the top-level composable that would generate a datetime composable. It internally calls the CustomDatePicker and CustomTimePicker composable. These two composables utilize the Dialog and Picker to display the date or time strings in a text field. The CustomDateTimePicker uses an mutable state object of type of LocalDateTime class. This object is set with the date and time picked by the user and displayed in the date and time text fields.

TFDateUtils.kt

The object provides different utility methods for manipulating LocalDate, LocalDateTime, String and Long milliseconds. These methods are used from the different composable methods in DateTimePicker.kt.

Code walk-through 

The DatePickerState and TimePickerState manage the state of the Date and Time picker. Initialized differently, I created mutable state strings to remember the date and time value to be displayed on the text fields. Ideally, I wanted to use the DatePickerState and TimePickerState to directly set the value in the text fields. But, these objects updated even when the user uses the picker, selects the data or time and cancels or dismisses the dialog. 

val displayDate = remember {
        mutableStateOf(TFDateUtils.getCurrentDateString(dateFormat))
    }

As mentioned earlier, jetpack compose does not have a TimePickerDialog. I used the composable written in the article Exploring Date and Time Pickers: Compose Bytes.

Lastly, in the CustomDateTimePicker composable, I created a lambda function to update the LocalDateTime with the DatePickerState and TimePickerState value (or the default initial values). This function is passed to the CustomDatePicker and CustomTimePicker composable, so the function can be called every time the user selects a new date or time.  

val updateState = {
        val dateString = if (dateState.selectedDateMillis != null) {
            TFDateUtils.convertMillisToDateString(
                millis = dateState.selectedDateMillis!!,
                format = dateFormat
            )
        } else {
            TFDateUtils.getCurrentDateString(dateFormat)
        }
        val timeString = if (timePickerState.minute != null 
                                               && timePickerState.hour != null) {
            TFDateUtils.getTimeString(
                hour = timePickerState.hour,
                min = timePickerState.minute
            )
        } else {
            TFDateUtils.getCurrentTimeString(timeFormat)
        }
        state.value = TFDateUtils.getLocalDataTimeFromString(
            dateTime = "$dateString $timeString",
            format = dateTimeFormat
        )
    }

I have used a Toast to display the value of the LocalDateTime state set by the CustomDateTimePicker composable. In the real-world, this will probably need a converter to convert to string and save it to Rooms database through a ViewModel. 

Known issues and improvements

  • The current implementation has some glitch when either date or time is selected but not both. The LocalDateTime state object needs to be synchronized better with the displayDate and displayTime mutable state strings to prevent this issue.
  • Using OffSetDateTime instead of LocalDateTime will help account for timezone, especially when the device moves between timezones. 

 

Hope the article provided a good starting point to create a custom datetime picker composable that can be reused by quickly adding to other composable.

 

 

 

 

 

No comments

Leave your comment

In reply to Some User