How to Add Time Selection to Jetpack Compose DatePicker

Jetpack Compose has revolutionized Android app development with its modern, declarative approach to UI creation. However, while Compose offers built-in components like DatePicker, developers often encounter scenarios where they need to integrate custom functionality, such as adding time selection.

In this blog post, we’ll explore advanced techniques to extend the Jetpack Compose DatePicker by incorporating time selection. This comprehensive guide targets intermediate to advanced Android developers, providing detailed steps, best practices, and reusable code snippets for integrating this feature seamlessly into your Compose application.

Why Combine Date and Time Pickers?

Many apps require both date and time inputs, such as scheduling, reminders, or booking systems. While Jetpack Compose provides a Material DatePicker, it doesn’t include native support for time selection. By combining a DatePicker and TimePicker, you can:

  • Offer a more intuitive user experience.

  • Ensure consistency in date and time data handling.

  • Streamline user workflows by reducing navigation between separate pickers.

Step 1: Setting Up Jetpack Compose Environment

Before diving into implementation, ensure your project is configured for Jetpack Compose:

  1. Add Compose dependencies in your build.gradle file:

    implementation "androidx.compose.material3:material3:1.2.0"
    implementation "androidx.compose.ui:ui:1.5.0"
    implementation "androidx.compose.foundation:foundation:1.5.0"
    implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.0"
    implementation "androidx.navigation:navigation-compose:2.6.0"
  2. Set the minSdkVersion to 21 or higher in your build.gradle file.

  3. Ensure your app theme supports Material 3 components.

Step 2: Implementing the Custom Date and Time Picker

Jetpack Compose allows us to build composable functions for customized UI elements. Below, we’ll create a unified component that integrates date and time selection.

Composable for Date Selection

Compose provides the DatePickerDialog from the Material Design library for selecting dates. Here's how to implement it:

@Composable
fun DatePickerDialog(
    selectedDate: LocalDate?,
    onDateSelected: (LocalDate) -> Unit,
    onDismissRequest: () -> Unit
) {
    val datePickerState = remember { DatePickerState() }

    MaterialDatePicker(
        state = datePickerState,
        onDismissRequest = onDismissRequest
    ) {
        onDateSelected(it)
    }
}

Composable for Time Selection

For time selection, Jetpack Compose doesn’t offer a native TimePicker, but we can build one using foundational components:

@Composable
fun TimePicker(
    selectedTime: LocalTime?,
    onTimeSelected: (LocalTime) -> Unit
) {
    val hour = remember { mutableStateOf(selectedTime?.hour ?: 0) }
    val minute = remember { mutableStateOf(selectedTime?.minute ?: 0) }

    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Select Time", style = MaterialTheme.typography.h6)

        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            NumberPicker(
                value = hour.value,
                range = 0..23,
                onValueChange = { hour.value = it }
            )
            Text(":", style = MaterialTheme.typography.h6)
            NumberPicker(
                value = minute.value,
                range = 0..59,
                onValueChange = { minute.value = it }
            )
        }

        Button(onClick = {
            onTimeSelected(LocalTime.of(hour.value, minute.value))
        }) {
            Text("OK")
        }
    }
}

NumberPicker Implementation

NumberPicker is a custom composable for scrolling numerical values:

@Composable
fun NumberPicker(
    value: Int,
    range: IntRange,
    onValueChange: (Int) -> Unit
) {
    LazyColumn(
        modifier = Modifier.height(100.dp)
    ) {
        items(range.toList()) { number ->
            Text(
                text = number.toString(),
                modifier = Modifier
                    .fillMaxWidth()
                    .clickable { onValueChange(number) },
                textAlign = TextAlign.Center
            )
        }
    }
}

Step 3: Combining Date and Time Pickers

To provide a unified interface, create a composable that combines the date and time pickers:

@Composable
fun DateTimePicker(
    initialDateTime: Pair<LocalDate, LocalTime>?,
    onDateTimeSelected: (LocalDate, LocalTime) -> Unit,
    onDismissRequest: () -> Unit
) {
    var selectedDate by remember { mutableStateOf(initialDateTime?.first) }
    var selectedTime by remember { mutableStateOf(initialDateTime?.second) }

    Column(modifier = Modifier.fillMaxSize()) {
        if (selectedDate == null) {
            DatePickerDialog(
                selectedDate = selectedDate,
                onDateSelected = { selectedDate = it },
                onDismissRequest = onDismissRequest
            )
        } else {
            TimePicker(
                selectedTime = selectedTime,
                onTimeSelected = {
                    selectedTime = it
                    onDateTimeSelected(selectedDate!!, selectedTime!!)
                }
            )
        }
    }
}

Step 4: Styling and Theming

Ensure your DateTimePicker aligns with your app’s theme:

  • Use Material 3 typography and color schemes.

  • Add padding, margins, and spacing to enhance user experience.

  • Test for accessibility, including color contrast and screen reader support.

Step 5: Best Practices and Performance Optimization

  1. State Management: Use remember and mutableStateOf for efficient state updates.

  2. Reusability: Modularize components for reuse across your app.

  3. Testing: Verify functionality on various screen sizes and Android versions.

  4. User Feedback: Display error messages for invalid inputs or incomplete selections.

Conclusion

Integrating time selection into Jetpack Compose’s DatePicker enhances user experience and fulfills essential app requirements. By following the techniques and best practices outlined in this guide, you can build a polished, professional DateTimePicker for your Compose application.

Leverage the power of Compose to streamline your app’s date and time input workflows. If you found this guide helpful, share it with your fellow developers, and let us know your thoughts in the comments below!