DEV Community

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

Posted on

14

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 😉

Sentry mobile image

Improving mobile performance, from slow screens to app start time

Based on our experience working with thousands of mobile developer teams, we developed a mobile monitoring maturity curve.

Read more

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

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

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay