Android’s TimePickerDialog is fairly simple to use and works well if all you need to do is to choose hours and minutes. The problem is that you can’t restrict which times can be selected, set time intervals, allow seconds to be chosen, or style it in a meaningful way.
The MaterialDateTimePicker library was made to fill in the gap.
In this post, I will cover some basics of using the TimePickerDialog in Kotlin and using data binding. I will assume you have done data binding before so won’t discuss it in detail.
Getting Started
To begin, you will want to include these in your app gradle file:
implementation 'com.wdullaer:materialdatetimepicker:4.1.0'
implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'
ThreeTenABP is an adaptation of the Java ThreeTen Backport, a LocalDateTime library. It’s used by the picker and is very simple to use.
The Layout
Before we can create the TimePicker, we need a button and a TextView that can display the selected time. As with most data binding in Kotlin, you need data, with the ViewModel being bound. The reason for this is that in the fragment, we can bind the ViewModel and use it within the layout. In this case, we’ll be calling android:text=”@{viewModel.timeText}” to databind the text.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example.myapplication.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.timeText}"
android:textColor="@android:color/black"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/choose_time"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="12:00 AM" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/choose_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Choose Time"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/time" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Storing and Displaying the Time
In the ViewModel, you will need LocalTime LiveData to hold the selected time, and probably String LiveData to display the time selected. The public LiveData for the selected time will be used in the fragment, depending on whether you are observing it or getting the value from it.
private val _selectedTime = MutableLiveData<LocalTime>()
val selectedTime: LiveData<LocalTime> = _selectedTime
fun setSelectedTime(time: LocalTime) {
_selectedTime.value = time
}
val timeText: LiveData<String> = Transformations.map(_selectedTime) {
it.format(DateTimeFormatter.ofPattern("hh:mm a"))
}
It’s likely that you will need other things in your ViewModel as well. For example, maybe you have two time selections for starting and ending times. In that case, the setSelectedStartTime method would need to check that the ending time is greater than the starting time. If it isn’t, it should update the ending time to begin just after the starting time, depending on the time intervals used, i.e., 30 minutes after the starting time.
Instantiating the TimePicker
In the fragment onActivityCreated method, once the ViewModel has been created and bound, the initial selected time needs to be set. Midnight would be appropriate, but that depends on the intent of the app.
mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
binding.viewModel = mainViewModel
mainViewModel.setSelectedTime(LocalTime.of(0, 0))
All that’s left is to add an onClickListener for our button. In it, we want to get the selected time and initialize the TimePickerDialog. The newInstance for the TimePicker also takes a function that’s called when the time is selected. We can set the hour and minute intervals, change the color, and show the dialog.
choose_time.setOnClickListener {
val startTime = mainViewModel.selectedTime.value ?: LocalTime.of(0, 0)
val startTimePicker = TimePickerDialog.newInstance({ _, hour, minute, _ ->
val time = LocalTime.of(hour, minute)
mainViewModel.setSelectedTime(time)
}, startTime.hour, startTime.minute, false)
startTimePicker.setTimeInterval(1, 30)
startTimePicker.accentColor = Color.parseColor("#D34568")
startTimePicker.show(fragmentManager, "Start Time Picker")
}
Note that in this example, the creation and setup of the TimePick can be done outside the click listener, with the picker stored in a variable and shown in the listener. The reason for showing it in the listener is that if you have something else in your view that could change the selected time, e.g. editing time entries, the only way to set the initial time when the dialog opens would be to create a new one.
That’s mainly it. You can find other useful methods in the TimePickerDialog, such as setting the minimum time, enabling seconds, changing the colors of various parts or their text, and other listeners.