Showing MaterialTimePicker: Using SupportFragmentManager in Android Compose

Michael Hsieh
4 min readAug 25, 2023

--

Photo by Aron Visuals on Unsplash

Introduction

Recently, I wanted to create a time picker in an Android app following Material Design. Google provides a MaterialTimePicker from their Material Components library to do this.

However, you need some extra steps to show the MaterialTimePicker. You need to get a FragmentManager and change your theme style.

Here’s each step to show the picker in a new Android Compose app:

MaterialTimePicker

Setup

Create a default Compose project in Android Studio.

First add the dependency to your app-level build.gradle:

implementation 'com.google.android.material:material:1.3.0'

Then put a picker in setContent in MainActivity. If you try to show it using MaterialTimePicker.Builder like below, you will get 2 errors:

package com.example.examplematerialtimepicker

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.example.examplematerialtimepicker.ui.theme.ExampleMaterialTimePickerTheme
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
ExampleMaterialTimePickerTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
// Build TimePicker and show
MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_12H)
.setHour(9)
.setMinute(30)
.setTitleText("Time of the meeting")
.build().apply {
addOnCancelListener { /* on cancel */ }
addOnDismissListener { /* on dismiss */ }
addOnPositiveButtonClickListener { "Selected time: $hour : $minute" }
addOnNegativeButtonClickListener { /* on negative button click */ }
}
.show(supportFragmentManager, FRAGMENT_TAG)
}
}
}
}
}

supportFragmentManager and FRAGMENT_TAG are unresolved references, because they’re not defined.

FragmentManager

The picker expects supportFragmentManager to be a FragmentManager. Why is FragmentManager required in the first place?

It turns out MaterialTimePicker extends DialogFragment.

Here’s a rough UML class diagram to illustrate that:

MaterialTimePicker is a subclass of DialogFragment. It inherits show() method.

The show() method comes from DialogFragment:

/**
Params:
manager – The FragmentManager this fragment will be added to.
tag – The tag for this fragment, as per FragmentTransaction.add.
*/

public void show(@NonNull FragmentManager manager, @Nullable String tag) {

The tag is just a string for the picker. You can define it outside the class:

private const val FRAGMENT_TAG = "time_picker_frag"

class MainActivity : ComponentActivity() {
// ...
}

Here we come to the next problem: By default, Android’s MainActivity extends ComponentActivity. It does not have a FragmentManager.

Solution: AppCompatActivity

One solution is to change MainActivity to extend AppCompatActivity. Then use its supportFragmentManager property.

This works because AppCompatActivity extends FragmentActivity which extends ComponentActivity. The relationship looks like this:

AppCompatActivity and FragmentActivity have supportFragmentManager. ComponentActivity does not.

Extend AppCompatActivity instead. Now the app can build, but by default it will crash with a different error when you run it:

package com.example.examplematerialtimepicker


import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.examplematerialtimepicker.ui.theme.ExampleMaterialTimePickerTheme
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat

private const val FRAGMENT_TAG = "time_picker_frag"

// Extend AppCompatActivity instead of ComponentActivity.
// AppCompatActivity extends FragmentActivity which extends ComponentActivity.
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
ExampleMaterialTimePickerTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_12H)
.setHour(9)
.setMinute(30)
.setTitleText("Time of the meeting")
.build().apply {
addOnCancelListener { /* on cancel */ }
addOnDismissListener { /* on dismiss */ }
addOnPositiveButtonClickListener { "Selected time: $hour : $minute" }
addOnNegativeButtonClickListener { /* on negative button click */ }
}
.show(supportFragmentManager, FRAGMENT_TAG)
}
}
}
}
}

The new error is related to the app theme:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.examplematerialtimepicker/com.example.examplematerialtimepicker.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

Solution: Change Theme

The theme is defined in AndroidManifest.xml. You should have a line like this in the application tag:

android:theme="@style/ExampleMaterialTimePickerTheme"

The style comes from your app folder’s res/values/themes.xml. The default file looks something like this:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ExampleMaterialTimePickerTheme" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

The problem here is the parent theme android:Theme.Material.Light.NoActionBar. It’s a general theme that doesn’t include all the attributes required by Material Components.

The solution is you should use a specific Material Components theme, for example Theme.MaterialComponents.Light:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ExampleMaterialTimePickerTheme" parent="Theme.MaterialComponents.Light" />
</resources>

Summary

TL;DR change your Activity to AppCompatActivity, get the supportFragmentManager, and make sure your parent theme works with the Material Components library.

Here is the complete code:

MainActivity.kt

package com.example.examplematerialtimepicker

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.example.examplematerialtimepicker.ui.theme.ExampleMaterialTimePickerTheme
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat

private const val FRAGMENT_TAG = "time_picker_frag"

// Extend AppCompatActivity instead of ComponentActivity.
// AppCompatActivity extends FragmentActivity which extends ComponentActivity.
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
ExampleMaterialTimePickerTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_12H)
.setHour(9)
.setMinute(30)
.setTitleText("Time of the meeting")
.build().apply {
addOnCancelListener { /* on cancel */ }
addOnDismissListener { /* on dismiss */ }
addOnPositiveButtonClickListener { "Selected time: $hour : $minute" }
addOnNegativeButtonClickListener { /* on negative button click */ }
}
.show(supportFragmentManager, FRAGMENT_TAG)
}
}
}
}
}

app/res/values/themes.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ExampleMaterialTimePickerTheme" parent="Theme.MaterialComponents.Light" />
</resources>

Hope this was useful. Happy coding.

--

--

Michael Hsieh

California-based Mobile App Developer. Software Engineer. I like sharing what I've learned with others.