DEV Community

Cover image for Datas no Jetpack Compose com Date Picker
Alex Felipe
Alex Felipe

Posted on

Datas no Jetpack Compose com Date Picker

Utilizar datas é uma tarefa bastante comum em diversos Apps, seja para um cadastro, agendamento, indicação de conclusão etc... Nas bibliotecas padrões do Jetpack Compose, não temos acesso a um componente para lidar com datas!

Em outras palavras, ou precisamos implementar manualmente um composable ou podemos usar os pickers do SDK do Android... Felizmente, a partir da versão 1.2.0 dos componentes do Material Design 3 para Jetpack Compose, temos acesso a novos pickers, como por exemplo, o DateTimerPicker!

E neste artigo eu vou te mostrar como usá-lo no seu App com o Jetpack Compose.

Verificando as dependências

Antes de tudo, é muito importante que você verifique a versão da dependência androidx.compose.material3:material3. Geralmente, as versões não são especificadas, ou seja, dependendo do momento que implementar, pode ser que utilize uma versão anterior a 1.2.0!

Nesse caso, vá até o build.gradle.kts e adicione manualmente a versão:

dependencies {
    // dependências
    implementation("androidx.compose.material3:material3:1.2.0-alpha02")
}
Enter fullscreen mode Exit fullscreen mode

Caso existir um aviso indicando que tem versões mais recentes, talvez não precise especificar a versão, pois provavelmente a 1.2.0 ou uma mais recente entrou em release.

Com a dependência adicionada, podemos implementar o composable.


TL;DR

Para você que quer apenas um código de amostra, segue a versão final do composable:

val focusManager = LocalFocusManager.current
var showDatePickerDialog by remember {
    mutableStateOf(false)
}
val datePickerState = rememberDatePickerState()
var selectedDate by remember {
    mutableStateOf("")
}
if (showDatePickerDialog) {
    DatePickerDialog(
        onDismissRequest = { showDatePickerDialog = false },
        confirmButton = {
            Button(
                onClick = {
                    datePickerState
                        .selectedDateMillis?.let { millis ->
                            selectedDate = millis.toBrazilianDateFormat()
                        }
                    showDatePickerDialog = false
                }) {
                Text(text = "Escolher data")
            }
        }) {
        DatePicker(state = datePickerState)
    }
}
TextField(
    value = selectedDate,
    onValueChange = { },
    Modifier
        .padding(8.dp)
        .fillMaxWidth()
        .onFocusEvent {
            if (it.isFocused) {
                showDatePickerDialog = true
                focusManager.clearFocus(force = true)
            }
        },
    label = {
        Text("Date")
    },
    readOnly = true
)
Enter fullscreen mode Exit fullscreen mode

O formatador de data você pode implementar como preferir, mas também pode usar essa extensão que criei:

fun Long.toBrazilianDateFormat(
    pattern: String = "dd/MM/yyyy"
): String {
    val date = Date(this)
    val formatter = SimpleDateFormat(
        pattern, Locale("pt-br")
    ).apply {
        timeZone = TimeZone.getTimeZone("GMT")
    }
    return formatter.format(date)
}
Enter fullscreen mode Exit fullscreen mode

Esse código deve apresentar o seguinte resultado:

App em execução, apresenta caixa de diálogo com o date picker ao clicar no campo de texto 'Date'. Ao clicar no botão 'Escolher data', fecha a caixa de diálogo e mostra a data formatada no padrão dd/MM/yyyy no campo de texto. Ao abrir novamente a caixa de diálogo e clicar fora do escopo, apenas fecha a caixa de diálogo.

Para entender as motivações desse código, acompanhe o restante do conteúdo. 😎


Utilizando o DatePicker na tela

A utilização do picker é relativamente simples:

val datePickerState = rememberDatePickerState()
DatePicker(state = datePickerState)
Enter fullscreen mode Exit fullscreen mode

App em execução apresentando o DatePicker na tela inteira

Veja que apenas chamando o composable DatePicker, temos uma tela dedicada para selecionar a data. Porém, apenas esse código não é o suficiente para obter a data escolhida!

Para isso, o DatePicker oferece a propriedade selectedDateMillis a partir do DatePickerState, mas, apenas ter acesso ao valor não vai entregar uma experiência de uso completa!

Adicionando o campo de texto para a data

Quando queremos apresentar uma data a partir de pickers, geralmente utilizamos um outro componente para abrir o picker e exibir a data escolhida, como por exemplo, em um formulário, teríamos um campo de texto pra isso:

var selectedDate by remember() {
    mutableStateOf("")
}
TextField(
    value = selectedDate,
    onValueChange = { },
    Modifier
        .padding(8.dp)
        .fillMaxWidth(),
    label = {
        Text("Date")
    },
    readOnly = true
)
Enter fullscreen mode Exit fullscreen mode

App em execução apresentando um campo de texto com a label 'Date'

Então, precisamos integrar o TextField com o DatePicker. Embora seja possível usar o DatePicker para essa solução, a implementação apenas com o picker tende ser um pouco mais complexa, pois existe a lógica para mostrá-lo ou escondê-lo...

Utilizando caixa de diálogo para o date picker

Para facilitar a implementação do DatePicker, vamos o DatePickerDialog. Basicamente, temos o comportamento padrão de caixa de diálogo do Android. Um dos casos comuns é não querer selecionar uma data, com a caixa de diálogo basta apenas clicar fora do contexto:

var selectedDate by remember {
    mutableStateOf("")
}
val datePickerState = rememberDatePickerState()
DatePickerDialog(
    onDismissRequest = { /*TODO*/ },
    confirmButton = { /*TODO*/ }) {
    DatePicker(state = datePickerState)
}
TextField(
    value = selectedDate,
    onValueChange = { },
    Modifier
        .padding(8.dp)
        .fillMaxWidth(),
    label = {
        Text("Date")
    },
    readOnly = true
)
Enter fullscreen mode Exit fullscreen mode

App em execução exibindo a caixa de dialogo apresentado o date picker

Agora, precisamos integrar a lógica para exibir o date picker apenas quando clicar no TextField.

Exibindo o DatePickerDialog a partir do TextField

Dado que usamos o readOnly, ele não reage mais a cliques! Logo, precisamos reagir a outros eventos, como a mudança de foco:

var showDatePickerDialog by remember {
    mutableStateOf(false)
}
var selectedDate by remember {
    mutableStateOf("")
}
val datePickerState = rememberDatePickerState()
if (showDatePickerDialog) {
    DatePickerDialog(
        onDismissRequest = { /*TODO*/ },
        confirmButton = { /*TODO*/ }) {
        DatePicker(state = datePickerState)
    }
}
TextField(
    value = selectedDate,
    onValueChange = { },
    Modifier
        .padding(8.dp)
        .fillMaxWidth()
        .onFocusChanged {
            if (it.isFocused) {
                showDatePickerDialog = true
            }
        },
    label = {
        Text("Date")
    },
    readOnly = true
)
Enter fullscreen mode Exit fullscreen mode

App em execução, ao clicar no campo de texto para data, abre a caixa de diálogo e exibe o Data Picker. Ao clicar fora do diálogo ele não é fechado

Veja que a caixa de diálogo exibe o date picker, porém, ele não é fechado ao clicar fora do seu contexto, um comportamento que não esperamos neste tipo de componente.

Implementando a lógica para fechar a caixa de diálogo

Para implementar essa solução, precisamos configurar os parâmetros onDismissRequest ou confirmButton e adicionar esse comportamento:

if (showDatePickerDialog) {
    DatePickerDialog(
        onDismissRequest = { showDatePickerDialog = false },
        confirmButton = {
            Button(onClick = { showDatePickerDialog = false }) {
                Text(text = "Escolher data")
            }
        }) {
        DatePicker(state = datePickerState)
    }
}
Enter fullscreen mode Exit fullscreen mode

App em execução, abrindo caixa de diálogo com Date Picker e botão de escolher data ao clicar no campo de texto 'Date'. Ao clicar fora do escopo do dialog, ele é fechado novamente. Ao tentar abrir novamente a caixa de diálogo, ela não é aberta

Observe que o comportamento para fechar a caixa de diálogo funciona, mas não é possível abrí-la novamente! Isso acontece, pois no Jetpack Compose, ao focar em um campo de texto, o evento de foco só muda se interagirmos com outro elemento que ganhe o foco, ou então, quando modificamos via código.

Manipulando o foco dos elementos

Dado que não temos um outro elemento para ganhar foco na tela, e também, depender de outro elemento para exibir o date picker, é uma péssima experiência de uso, nós iremos modificar o foco manualmente!

Para isso, utilizamos o gerenciador de foco e limpamos o foco no evento desejado, nesse caso, ao ganhar o foco no TextField:

val focusManager = LocalFocusManager.current
...
TextField(
    value = selectedDate,
    onValueChange = { },
    Modifier
        .padding(8.dp)
        .fillMaxWidth()
        .onFocusEvent {
            if (it.isFocused) {
                showDatePickerDialog = true
                focusManager.clearFocus(force = true)
            }
        },
    label = {
        Text("Date")
    },
    readOnly = true
)
Enter fullscreen mode Exit fullscreen mode

App em execução, ao clicar no campo de texto 'Date', a caixa diálogo com o Date Picker é apresentada. Ao clicar fora do escopo da caixa de diálogo, a caixa de diálogo é fechada e apresenta o campo de texto 'Date' sem foco, ao clicar novamente no campo de texto, abre a caixa de diálogo. Ao clicar no botão 'Escolher data' a caixa de diálogo é fechada também

Veja que agora a caixa de diálogo é aberta e fechada nas situações esperadas! O que falta é pegar o valor da data escolhida e apresentá-la no campo de texto.

Exibindo a data escolhida no TextField

Para isso, precisamos reagir ao evento correto que deve preencher o campo de texto. No contexto atual, temos o botão 'Escolher data', ele será o responsável em manipular o estado selectedDate que preenche o TextField.

Formatando data em Long (milisegundos) para String

Considerando que a exibição de datas em Apps geralmente não é apresentada via milisegundos, vamos adicionar um formatador de Long para String que faça isso pra gente, podemos até mesmo considerar uma extension:

fun Long.toBrazilianDateFormat(
    pattern: String = "dd/MM/yyyy"
): String {
    val date = Date(this)
    val formatter = SimpleDateFormat(
        pattern, Locale("pt-br")
    ).apply {
        timeZone = TimeZone.getTimeZone("GMT")
    }
    return formatter.format(date)
}
Enter fullscreen mode Exit fullscreen mode

E com a função de conversão pronta, precisamos apenas ajustar o evento de clique do botão:

DatePickerDialog(
    onDismissRequest = { showDatePickerDialog = false },
    confirmButton = {
        Button(
            onClick = {
                datePickerState
                    .selectedDateMillis?.let { millis ->
                        selectedDate = millis.toBrazilianDateFormat()
                    }
                    showDatePickerDialog = false
            }) {
            Text(text = "Escolher data")
        }
    }) {
    DatePicker(state = datePickerState)
}
...
Enter fullscreen mode Exit fullscreen mode

App em execução, apresenta caixa de diálogo com o date picker ao clicar no campo de texto 'Date'. Ao clicar no botão 'Escolher data', fecha a caixa de diálogo e mostra a data formatada no padrão dd/MM/yyyy no campo de texto. Ao abrir novamente a caixa de diálogo e clicar fora do escopo, apenas fecha a caixa de diálogo.

Veja que agora o nosso date picker funciona corretamente!

O que achou dessa implementação de date picker no Jetpack Compose? Gostou do conteúdo? Aproveita para deixar um like e compartilhar com a galera 😉

Top comments (3)

Collapse
 
phtrebilcock profile image
Pedro

Gostei da implementação, professor. Eu já tinha me enrolado com datapicker, mas gostei bastante da aplicação com o compose. Logo vou tentar usar em algum projeto meu.

Collapse
 
elivandrosantos profile image
Elivandro Santos • Edited

Muito obrigado professor, estou programando um aplicativo Android e, coloquei um OutlinedTextField que solicita data, quando programei, tinha que colocar o texto digitando.

Fiquei um bom tempo olhando para aquele campo e vi que não era produtivo ficar digitando a data, já que o aplicativo precisa de uma certa agilidade da pessoa que irá usar o aplicativo.

Pesquisando encontrei essas dicas super valiosas e me ajudou implementar a data no campo solicitado.

Muito obrigado.

Att.

Elivandro Santos

Collapse
 
alexfelipe profile image
Alex Felipe

Show demais que o conteúdo foi útil pra vc, Elivandro! Atualmente estou criando bastante conteúdo no meu canal do youtube, acompanha lá pra mais novidades youtube.com/@AlexFelipeDev