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") }
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 )
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) }
Esse código deve apresentar o seguinte resultado:
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)
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 )
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 )
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 )
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) } }
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 )
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) }
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) } ...
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)
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.
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
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