Set a Default Initial Date in Jetpack Compose DatePicker

Jetpack Compose has revolutionized Android development by offering a modern, declarative way to build UI components. One of the common UI elements developers often work with is the DatePicker. While using a DatePicker in Jetpack Compose is straightforward, setting a default initial date requires a deeper understanding of state management and Compose APIs. In this blog post, we’ll explore how to set a default initial date in a Jetpack Compose DatePicker, examine best practices, and provide advanced tips for customization.

Understanding the Jetpack Compose DatePicker

The DatePicker in Jetpack Compose is part of the androidx.compose.material or androidx.compose.material3 library, depending on your Material Design preference. Unlike the traditional XML-based approach, DatePicker in Compose integrates seamlessly with Compose’s declarative state management.

Before diving into setting a default date, let’s create a basic DatePicker using Jetpack Compose:

@Composable
fun BasicDatePicker() {
    val context = LocalContext.current
    val calendar = Calendar.getInstance()

    val year = calendar.get(Calendar.YEAR)
    val month = calendar.get(Calendar.MONTH)
    val day = calendar.get(Calendar.DAY_OF_MONTH)

    var selectedDate by remember { mutableStateOf("Select a date") }

    Button(onClick = {
        DatePickerDialog(
            context,
            { _, selectedYear, selectedMonth, selectedDay ->
                selectedDate = "$selectedDay/${selectedMonth + 1}/$selectedYear"
            },
            year, month, day
        ).show()
    }) {
        Text(text = selectedDate)
    }
}

This implementation shows a DatePickerDialog when the button is clicked, allowing the user to select a date.

Setting a Default Initial Date

To set a default initial date in the DatePicker, you need to specify the default year, month, and day values when initializing the dialog. Here’s how you can achieve that:

@Composable
fun DefaultDatePicker(defaultYear: Int, defaultMonth: Int, defaultDay: Int) {
    val context = LocalContext.current

    var selectedDate by remember { mutableStateOf("Select a date") }

    Button(onClick = {
        DatePickerDialog(
            context,
            { _, selectedYear, selectedMonth, selectedDay ->
                selectedDate = "$selectedDay/${selectedMonth + 1}/$selectedYear"
            },
            defaultYear, defaultMonth, defaultDay
        ).show()
    }) {
        Text(text = selectedDate)
    }
}

Passing Default Values Dynamically

In most real-world scenarios, the default date might be dynamic, based on user preferences or backend data. You can pass these default values to the composable as parameters or derive them from a ViewModel.

@Composable
fun DynamicDefaultDatePicker() {
    val defaultCalendar = Calendar.getInstance().apply {
        set(2025, 0, 1) // Default to January 1, 2025
    }

    DefaultDatePicker(
        defaultYear = defaultCalendar.get(Calendar.YEAR),
        defaultMonth = defaultCalendar.get(Calendar.MONTH),
        defaultDay = defaultCalendar.get(Calendar.DAY_OF_MONTH)
    )
}

Best Practices for Using Default Dates

1. Use UTC Timezone for Consistency

When working with dates, always use UTC to avoid timezone-related issues. This ensures consistent behavior across different devices and regions.

val utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))

2. Leverage ViewModel for State Management

Use a ViewModel to manage the default date and selected date. This decouples the UI from business logic and allows for better testability.

class DatePickerViewModel : ViewModel() {
    private val _defaultDate = MutableLiveData(Calendar.getInstance())
    val defaultDate: LiveData<Calendar> = _defaultDate

    fun updateDefaultDate(year: Int, month: Int, day: Int) {
        _defaultDate.value = Calendar.getInstance().apply {
            set(year, month, day)
        }
    }
}

@Composable
fun ViewModelDatePicker(viewModel: DatePickerViewModel) {
    val defaultCalendar by viewModel.defaultDate.observeAsState(Calendar.getInstance())

    DefaultDatePicker(
        defaultYear = defaultCalendar.get(Calendar.YEAR),
        defaultMonth = defaultCalendar.get(Calendar.MONTH),
        defaultDay = defaultCalendar.get(Calendar.DAY_OF_MONTH)
    )
}

3. Provide a User-Friendly Format

When displaying the selected date, format it using libraries like java.text.SimpleDateFormat or java.time.format.DateTimeFormatter.

val formatter = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
val formattedDate = formatter.format(selectedCalendar.time)

Advanced Customization

Custom DatePicker Dialog

You can create a fully customized DatePicker using Compose components instead of relying on the DatePickerDialog.

@Composable
fun CustomDatePicker(onDateSelected: (Int, Int, Int) -> Unit) {
    val calendar = Calendar.getInstance()

    var selectedYear by remember { mutableStateOf(calendar.get(Calendar.YEAR)) }
    var selectedMonth by remember { mutableStateOf(calendar.get(Calendar.MONTH)) }
    var selectedDay by remember { mutableStateOf(calendar.get(Calendar.DAY_OF_MONTH)) }

    Column(modifier = Modifier.padding(16.dp)) {
        Text(text = "Select a date")
        Row {
            TextField(
                value = selectedYear.toString(),
                onValueChange = { selectedYear = it.toIntOrNull() ?: selectedYear },
                label = { Text("Year") }
            )
            TextField(
                value = (selectedMonth + 1).toString(),
                onValueChange = { selectedMonth = (it.toIntOrNull() ?: 1) - 1 },
                label = { Text("Month") }
            )
            TextField(
                value = selectedDay.toString(),
                onValueChange = { selectedDay = it.toIntOrNull() ?: selectedDay },
                label = { Text("Day") }
            )
        }
        Button(onClick = { onDateSelected(selectedYear, selectedMonth, selectedDay) }) {
            Text("Confirm")
        }
    }
}

Integrating With Backend APIs

For scenarios involving server-side date configurations, retrieve the default date from an API and update your Compose state dynamically.

suspend fun fetchDefaultDate(): Calendar {
    val response = api.getDefaultDate() // Example API call
    return Calendar.getInstance().apply {
        set(response.year, response.month, response.day)
    }
}

Conclusion

Setting a default initial date in a Jetpack Compose DatePicker is a simple yet essential feature that enhances user experience. By following best practices such as using ViewModel for state management, ensuring timezone consistency, and leveraging advanced customizations, you can create robust and user-friendly date pickers.

Jetpack Compose empowers developers to build dynamic and responsive UIs with minimal effort, and mastering components like the DatePicker will elevate your Android development skills. Experiment with the provided examples and adapt them to your project needs to make the most of Jetpack Compose’s capabilities.