Custom Material3 Jetpack Compose Date picker

Material3 has two components that are easy to use when we need to use dates.

  • DatePicker
  • DialogDatePicker

DatePicker

This component is straightforward to use. With just few lines you can have basic functionality

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomDatePicker() {
    val state = rememberDatePickerState()
    DatePicker(state = state)
}

DatePickerDialog

This component it is just a dialog for holding the datepicker

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomDatePicker() {
    val state = rememberDatePickerState()
    DatePickerDialog(
        onDismissRequest = {  },
        confirmButton = { }) {
      DatePicker(state = state)
    }
}

Using both components we can build a a custom datepicker acording our requeriment. In my case, I just needed a TextField that shows the Date and that shows the Dialog when it is clicked.

1. First, we put a TextField and a Button

val date = remember { mutableStateOf(LocalDate.now())}
    Row(verticalAlignment = Alignment.CenterVertically) {
        OutlinedTextField(
            readOnly = true,
            value = date.value.format(DateTimeFormatter.ISO_DATE),
            label = { Text("Date") },
            onValueChange = {})
        IconButton(
            onClick = { }
        ) {
            Icon(imageVector = Icons.Default.CalendarMonth, contentDescription = "Calendar")
        }
    }

2. Next, we add a new composable with the dialog

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomDatePickerDialog(
    onAccept: (Long?) -> Unit,
    onCancel: () -> Unit
) {
    val state = rememberDatePickerState()
    
    DatePickerDialog(
        onDismissRequest = { },
        confirmButton = {
            Button(onClick = { onAccept(state.selectedDateMillis) }) {
                Text("Accept")
            }
        },
        dismissButton = {
            Button(onClick = onCancel) {
                Text("Cancel")
            }
        }
    ) {
        DatePicker(state = state)
    }
}

3. Then we add a variable to show/hide the dialog

val isOpen = remember { mutableStateOf(false)}

And we set the value using the onAccept and onCancel events

@Composable
fun CustomDatePicker() {
    val date = remember { mutableStateOf(LocalDate.now())}
    val isOpen = remember { mutableStateOf(false)}
    Row(verticalAlignment = Alignment.CenterVertically) {

        OutlinedTextField(
            readOnly = true,
            value = date.value.format(DateTimeFormatter.ISO_DATE),
            label = { Text("Date") },
            onValueChange = {})

        IconButton(
            onClick = { isOpen.value = true } // show de dialog
        ) {
            Icon(imageVector = Icons.Default.CalendarMonth, contentDescription = "Calendar")
        }
    }

    if (isOpen.value) {
        CustomDatePickerDialog(
            onAccept = {
                isOpen.value = false // close dialog
            },
            onCancel = {
                isOpen.value = false //close dialog
            }
        )
    }
}

4. Finally, we set the value in the TextField

CustomDatePickerDialog(
        onAccept = {
            isOpen.value = false // close dialog

            if (it != null) { // Set the date
                date.value = Instant
                    .ofEpochMilli(it)
                    .atZone(ZoneId.of("UTC"))
                    .toLocalDate()
            }
        },
        onCancel = {
            isOpen.value = false //close dialog
        }
    )

The whole code looks like this

@Composable
fun CustomDatePicker() {
    val date = remember { mutableStateOf(LocalDate.now())}
    val isOpen = remember { mutableStateOf(false)}

    Row(verticalAlignment = Alignment.CenterVertically) {

        OutlinedTextField(
            readOnly = true,
            value = date.value.format(DateTimeFormatter.ISO_DATE),
            label = { Text("Date") },
            onValueChange = {})

        IconButton(
            onClick = { isOpen.value = true } // show de dialog
        ) {
            Icon(imageVector = Icons.Default.CalendarMonth, contentDescription = "Calendar")
        }
    }

    if (isOpen.value) {
        CustomDatePickerDialog(
            onAccept = {
                isOpen.value = false // close dialog

                if (it != null) { // Set the date
                    date.value = Instant
                        .ofEpochMilli(it)
                        .atZone(ZoneId.of("UTC"))
                        .toLocalDate()
                }
            },
            onCancel = {
                isOpen.value = false //close dialog
            }
        )
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomDatePickerDialog(
    onAccept: (Long?) -> Unit,
    onCancel: () -> Unit
) {
    val state = rememberDatePickerState()

    DatePickerDialog(
        onDismissRequest = { },
        confirmButton = {
            Button(onClick = { onAccept(state.selectedDateMillis) }) {
                Text("Accept")
            }
        },
        dismissButton = {
            Button(onClick = onCancel) {
                Text("Cancel")
            }
        }
    ) {
        DatePicker(state = state)
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *