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