Los Date Pickers son componentes que permiten al usuario seleccionar fechas o rangos de fechas. Estos son incorporados en diálogos debido a que su diseño ocupa gran área de la pantalla.
Adicionalmente, disponen de un modo Input en el cual la selección se da por un campo de texto:
La idea de este tutorial es que aprendas a implementar tanto un Date Picker como un Date Range Picker en Jetpack Compose.
Veamos cómo hacerlo.
Proyecto En Android Studio Para Date Pickers
1. En primer lugar crearemos un nuevo proyecto en Android Studio con la plantilla de creación Empty Activity de tipo Jetpack Compose. Nombralo como “Date Pickers”.
2. Una vez construido el proyecto, crearemos un archivo llamado DatePickersScreen.kt
donde declararemos una función @Composable
con el mismo nombre:
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun DatePickersScreen() {
Scaffold(
topBar = { TopBar() },
content = { padding ->
DatePickersContent(
padding = padding
)
}
)
}
@Preview
@Composable
private fun Preview() {
DatePickersTheme {
DatePickersScreen()
}
}
3. La función DatePickersContent()
la ubicamos en un nuevo archivo DatePickersContent.kt
. Esta contiene a cada elemento de los ejemplos que veremos:
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun DatePickersContent(
padding: PaddingValues
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
// Futuro contenido
}
}
4. Luego abre MainActivity
y reemplaza la invocación por defecto por la llamada a DatePickersScreen()
.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DatePickersTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
DatePickersScreen()
}
}
}
}
}
5. Y debido a que vamos a usar fechas, habilitamos el Desugaring del JDK 8 para usar el paquete java.time
. Esto lo logras añadiendo las siguientes líneas al archivo build.gradle.kts
del módulo :app
.
android {
//..
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
//..
}
dependencies {
// Desugaring JDK
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
// ...
}
Con estas preparaciones, ya te es posible comenzar a añadir el código de los ejemplos del tutorial.
Si deseas ver el código final de este proyecto puedes acceder al repositorio de GitHub. Te pido le des una estrella ✨como señal de tu apoyo:
Ahora sí, pasemos a ver el primer ejemplo sobre date pickers.
Crear Un Date Picker
Caso De Uso:
Este ejemplo consiste de un Text Field que permite al usuario seleccionar una fecha. Para ello, este debe hacer click en el Leading Icon, lo que inicia el diálogo con un Date Picker en su interior. Una vez un valor sea confirmado, el valor del campo de texto será actualizado con la fecha formateada.
Herramientas:
Se requieren de dos componentes, a DatePickerDialog()
para el diálogo y DatePicker()
para la vista del selector de fechas.
Para DatePickerDialog
tenemos la siguiente firma:
@ExperimentalMaterial3Api
@Composable
fun DatePickerDialog(
onDismissRequest: () -> Unit,
confirmButton: @Composable () -> Unit,
modifier: Modifier = Modifier,
dismissButton: (@Composable () -> Unit)? = null,
shape: Shape = DatePickerDefaults.shape,
tonalElevation: Dp = DatePickerDefaults.TonalElevation,
colors: DatePickerColors = DatePickerDefaults.colors(),
properties: DialogProperties = DialogProperties(usePlatformDefaultWidth = false),
content: @Composable ColumnScope.() -> Unit
): Unit
En el ejemplo usaremos los siguientes parámetros:
onDismissRequest
: Es la acción a ejecutar cuando se quiere desvanecer al diálogoconfirmButton
: Componente para el botón de confirmacióndismissButton
: Componente para slot de botón de cancelacióncontent
: Componente para el date picker
En el caso de DatePicker()
tenemos:
@ExperimentalMaterial3Api
@Composable
fun DatePicker(
state: DatePickerState,
modifier: Modifier = Modifier,
dateFormatter: DatePickerFormatter = remember { DatePickerDefaults.dateFormatter() },
title: (@Composable () -> Unit)? = {
DatePickerDefaults.DatePickerTitle(
displayMode = state.displayMode,
modifier = Modifier.padding(DatePickerTitlePadding)
)
},
headline: (@Composable () -> Unit)? = {
DatePickerDefaults.DatePickerHeadline(
selectedDateMillis = state.selectedDateMillis,
displayMode = state.displayMode,
dateFormatter = dateFormatter,
modifier = Modifier.padding(DatePickerHeadlinePadding)
)
},
showModeToggle: Boolean = true,
colors: DatePickerColors = DatePickerDefaults.colors()
): Unit
La mayoría de sus parámetros tienen valores por defecto que nos facilita la invocación de DatePicker()
. Por lo que solo usaremos a state
, para obtener la fecha seleccionada a través de la propiedad DatePickerState.selectedDateMillis
.
Solución:
Crear Estado
Lo primero será crear el estado para el diálogo del date picker. ¿De qué consiste su UI?
- La fecha seleccionada
- La visibilidad del selector
- Click en confirmación
- Click en descarte
Las propiedades anteriores las declaramos en una nueva clase de datos llamada DatePickerDialogState
:
data class DatePickerDialogState(
val date: Long?,
val isPickerVisible: Boolean,
val onStart: () -> Unit,
val onConfirmationClick: (Long?) -> Unit,
val onDismissClick: () -> Unit
){
val isPickerHidden get() = !isPickerVisible
}
Crear ViewModel
Para mutar el estado y ejecutar acciones para los eventos de UI, crearemos la clase DatePickerDialogViewModel
:
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class DatePickerDialogViewModel : ViewModel() {
private val _state = MutableStateFlow(
DatePickerDialogState(
date = null,
isPickerVisible = false,
onStart = ::onStart,
onDismissClick = ::onDismissClick,
onConfirmationClick = ::onConfirmationClick
)
)
val state = _state.asStateFlow()
private fun onStart() {
_state.update { it.copy(isPickerVisible = true) }
}
private fun onDismissClick() {
_state.update { it.copy(isPickerVisible = false) }
}
private fun onConfirmationClick(date: Long?) {
_state.update { it.copy(isPickerVisible = false, date = date) }
}
}
Si prestas atención, hemos creado tres funciones para procesar los eventos propuestos en el estado:
onStart()
: Muestra el pickeronConfirmationClick()
: Oculta el picker y actualiza la fecha seleccionadaonDismissClick()
: Oculta el picker
Crear Date Picker Dialog
El siguiente paso es implementar la interfaz gráfica en una función de Compose. Para ello crea el archivo CustomDatePickerDialog.kt
y añade una función con el mismo nombre.
Esta debe:
- Parámetros: Recibir el estado que hemos creado previamente y un parámetro tipo función que comunica al exterior el momento en que se confirma la selección de la fecha
- Prevenir la invocación del diálogo si este está oculto
- Estados internos: Inicializar estado de tipo
DatePickerState
con la utilidadrememberDatePickerState()
y pasar ainitialSelectedDateMillis
la fecha de nuestro estado - Invocar a
DatePickerDialog()
onDismissRequest
: Le pasamos la propiedadonDismissClick
confirmButton
: Creamos un elementoTextButton()
, cuyo parámetroonClick
es la invocación deonConfirmationClick
yonDateSelected
. Además, deshabilitamos el botón si la fecha seleccionada esnull
.dismissButton
: Creamos unTextButton
y pasamos aonDismissClick
en su accióncontent
: Invocamos a la funciónDatePicker()
coninternalState
Implementando el algoritmo anterior en código tendrás:
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun CustomDatePickerDialog(
state: DatePickerDialogState,
onDateSelected: (Long?) -> Unit
) {
if (state.isPickerHidden) return
val internalState = rememberDatePickerState(
initialSelectedDateMillis = state.date
)
DatePickerDialog(
onDismissRequest = state.onDismissClick,
confirmButton = {
TextButton(
onClick = {
state.onConfirmationClick(internalState.selectedDateMillis)
onDateSelected(internalState.selectedDateMillis)
},
enabled = internalState.selectedDateMillis != null
) {
Text("Aceptar")
}
},
dismissButton = {
TextButton(onClick = state.onDismissClick) {
Text("Cancelar")
}
},
content = { DatePicker(state = internalState) },
)
}
Mostrar Fecha En Text Field
Para iniciar a CustomDatePickerDialog
y mostrar la fecha seleccionada, necesitamos modificar nuestra función componible del contenido principal.
Los siguientes son los pasos para realizarlo:
1. Crea el estado del contenido principal en una nueva clase de datos llamada DatePickersState
. Declara una propiedad por las siguientes datos e interacciones:
- Fecha seleccionada
- Rango seleccionado
- Click en botón de fecha
- Click en botón de rango
Implementado es:
data class DatePickersState(
val dateSelected: String,
val rangeSelected: String,
val onDateSelected: (Long?) -> Unit,
val onRangeSelected: (Long?, Long?) -> Unit
)
2. Luego creamos el viewmodel para sostener el estado anterior y responder a los clicks:
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class DatePickersViewModel : ViewModel() {
private val _state = MutableStateFlow(
DatePickersState(
dateSelected = "Seleccionar fecha",
rangeSelected = "Seleccionar rango",
onDateSelected = ::onDateConfirmation,
onRangeSelected = ::onRangeConfirmation
)
)
val state = _state.asStateFlow()
fun onDateConfirmation(date: Long?) {
_state.update { it.copy(dateSelected = formatDate(date)) }
}
fun onRangeConfirmation(startDate: Long?, endDate: Long?) {
_state.update { it.copy(rangeSelected = formatRange(startDate, endDate)) }
}
}
3. Los métodos onDateConfirmation()
y onRangeConfirmation()
usan funciones que formatean los milisegundos a Strings con un patrón de fechas presentables.
Para lograr este cometido usamos el paquete java.time
y la clase DateTimeFormatter
. Dichas utilidades las ubicaremos en un nuevo archivo DateExtensions.kt
:
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
fun formatDate(date: Long?): String {
return date?.asInstant()?.asShortString() ?: "Seleccionar fecha"
}
fun formatRange(start: Long?, end: Long?): String {
if (start == null || end == null) return "Seleccionar rango"
val startDate = Instant.ofEpochMilli(start).asShortString()
val endDate = Instant.ofEpochMilli(end).asShortString()
return "De $startDate a $endDate"
}
fun Instant.asShortString(): String {
return format("dd/MM/yyyy")
}
fun Instant.asMonthAndDayString(): String {
return format("MMM dd").replaceFirstChar { it.titlecase() }
}
fun Instant.format(pattern: String): String {
return DateTimeFormatter
.ofPattern(pattern)
.withZone(ZoneOffset.UTC)
.format(this)
}
fun Long?.asInstant(): Instant? = this?.let { Instant.ofEpochMilli(it) }
4. Mostrar el Date Picker requiere de un elemento de interacción. En nuestro caso es el botón inicial de un campo de texto que se encuentra en la pantalla principal.
Esto significa que es momento de modificar la función DatePickersContent()
. En ella, necesitamos:
- Parámetros: El padding del Scaffold, el estado principal, la función al clickarse el botón para iniciar el date picker y la función para iniciar el date range picker (lo veremos más adelante)
- Contenedor principal: Usaremos un layout
Column()
para ordenar un texto y un Text Field verticalmente OutlinedTextField()
value
: Será la propiedadDatePickersState.dateSelected
trailingIcon
: Asignaremos como argumento al parámetroonDateButtonClick
Planteado lo anterior, el código resultante es:
@Composable
fun DatePickersContent(
padding: PaddingValues,
state: DatePickersState,
onDateButtonClick: () -> Unit,
onRangeButtonClick: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
Text("Date Picker")
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = state.dateSelected,
onValueChange = {},
readOnly = true,
leadingIcon = {
IconButton(onClick = onDateButtonClick) {
Icon(imageVector = Icons.Filled.CalendarToday, contentDescription = null)
}
}
)
}
}
5. Luego inicializa los view models del date picker y del contenido principal. Con dichas instancias, ya es posible leer sus estados para invocar a CustomDatePickerDialog()
:
@Composable
fun DatePickersScreen(
datePickerViewModel: DatePickerDialogViewModel = viewModel(),
mainContentViewModel: DatePickersViewModel = viewModel()
) {
val datePickerState by datePickerViewModel.state.collectAsState()
val mainContentState by mainContentViewModel.state.collectAsState()
CustomDatePickerDialog(
state = datePickerState,
onDateSelected = mainContentState.onDateSelected
)
Scaffold(
topBar = { TopBar() },
content = { padding ->
DatePickersContent(
padding = padding,
state = mainContentState,
onDateButtonClick = datePickerState.onStart
)
}
)
}
Observa que tanto el diálogo le confirma la selección de fecha al contenido principal con onDateSelected
; y el contenido principal le confirma al diálogo su inicio con onStart
.
Termina este ejemplo, corriendo el proyecto Android Studio y comprobando el funcionamiento de la selección de fechas.
Crear Date Range Picker
Caso De Uso:
Este ejemplo es similar al anterior, la diferencia es que esta vez seleccionaremos dos fechas para establecer un rango y presentarlo en el contenido del Text Field.
Herramientas:
La creación de un selector de rango de fechas involucra la creación del diálogo con AlertDialog()
y la creación del widget con DateRangePicker()
.
De la función DateRangePicker()
solo usaremos su parámetro state
de tipo DateRangePickerState
, cuya firma recibe los siguientes parámetros:
@ExperimentalMaterial3Api
@Composable
fun DateRangePicker(
state: DateRangePickerState,
modifier: Modifier = Modifier,
dateFormatter: DatePickerFormatter = remember { DatePickerDefaults.dateFormatter() },
title: (@Composable () -> Unit)? = {
DateRangePickerDefaults.DateRangePickerTitle(
displayMode = state.displayMode,
modifier = Modifier.padding(DateRangePickerTitlePadding)
)
},
headline: (@Composable () -> Unit)? = {
DateRangePickerDefaults.DateRangePickerHeadline(
selectedStartDateMillis = state.selectedStartDateMillis,
selectedEndDateMillis = state.selectedEndDateMillis,
displayMode = state.displayMode,
dateFormatter,
modifier = Modifier.padding(DateRangePickerHeadlinePadding)
)
},
showModeToggle: Boolean = true,
colors: DatePickerColors = DatePickerDefaults.colors()
): Unit
También usaremos el parámetro headline
para personalizar la presentación de las fechas en el rango.
Solución:
Crear Estado
El estado para un selector de rangos es exactamente igual al del date picker. La única diferencia es la fecha adicional. Teniendo en cuenta crea la data class DateRangePickerDialogState
:
data class DateRangePickerDialogState(
val startDate: Long?,
val endDate: Long?,
val isPickerVisible: Boolean,
val onStart:()->Unit,
val onConfirmationClick: (Long?, Long?) -> Unit,
val onDismissClick: () -> Unit
) {
val isPickerHidden get() = !isPickerVisible
}
Cómo ves, el rango se representa por [startDate
, endDate
]; y onConfirmationClick
recibe ambos argumentos para mutar el valor actual.
Crear ViewModel
Acto seguido, añade la clase RangeDatePickerDialogViewModel
. Declara el estado y las funciones que responden a los eventos de UI como hicimos con DatePickerDialogViewModel
. La única diferencia a remarcar es la existencia de una segunda fecha:
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
class RangeDatePickerDialogViewModel : ViewModel() {
private val _state = MutableStateFlow(
DateRangePickerDialogState(
startDate = null,
endDate = null,
isPickerVisible = false,
onStart = ::onStart,
onConfirmationClick = ::onRangeConfirmationClick,
onDismissClick = ::onDismissClick
)
)
val state = _state.asStateFlow()
private fun onStart() {
_state.update { it.copy(isPickerVisible = true) }
}
private fun onRangeConfirmationClick(startDate: Long?, endDate: Long?) {
_state.update {
it.copy(startDate = startDate, endDate = endDate, isPickerVisible = false)
}
}
private fun onDismissClick() {
_state.update { it.copy(isPickerVisible = false) }
}
}
Crear Interfaz
Pasemos a crear un nuevo archivo Kotlin llamado CustomDateRangePickerDialog.kt
con la función @Composable
que declarará el diálogo para la selección de rangos.
¿Cómo construirla?
- Parámetros: Pasa el estado y un tipo función para confirmar al exterior la selección del rango
- Visibilidad: Antes de toda declaración, verifica si el date range picker está oculto, para evitar el dibujado en la composición
- Estados internos: Construye el estado inicial del tipo
DateRangPickerState
con la función de utilidadrememberDateRangePickerState()
. Pasa las fechas de nuestro estado a los parámetrosinitialSelectedStartDateMillis
yinitialSelectedEndDateMillis
- Invoca el componente
AlertDialog()
properties
: Pasa un elementoDialogProperties
, donde su propiedadusePlatformDefaultWidth
sea determinada por el valor dedisplayMode
del range picker. Esto permitirá expandir el diálogo completamente si está en modoDisplayMode.Picker
onDismissRequest
: Pasamos la propiedadonDismissClick
content
: Dibujaremos unaSurface
que contenga unaColumn
, la cual distribuirá los botones del diálogo y el componenteDateRangePicker()
La implementación con respecto a los pasos anteriores es:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomDateRangePickerDialog(
state: DateRangePickerDialogState,
onConfirmation: (Long?, Long?) -> Unit
) {
if (state.isPickerHidden) return
val internalState = rememberDateRangePickerState(
initialSelectedStartDateMillis = state.startDate,
initialSelectedEndDateMillis = state.endDate
)
val headline by remember { derivedStateOf { internalState.headline } }
AlertDialog(
properties = DialogProperties(usePlatformDefaultWidth = internalState.displayMode == DisplayMode.Input),
onDismissRequest = state.onDismissClick,
content = {
Surface(shape = MaterialTheme.shapes.large) {
Column(
modifier = Modifier.dateRangeDialogModifier(internalState),
verticalArrangement = Arrangement.Top
) {
ModalButtons(state, internalState, onConfirmation)
DateRangePicker(
state = internalState,
headline = {
Text(headline, modifier = Modifier.padding(start = 64.dp))
}
)
InputButtons(state, internalState, onConfirmation)
}
}
}
)
}
Si te fijas, las funciones ModalButtons()
e InputButtons()
son los botones asociados según el tipo de visualización. Ambos reciben los estados para definir su visibilidad y delegan las propiedades para confirmación y cancelación de la selección.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ColumnScope.InputButtons(
state: DateRangePickerDialogState,
pickerState: DateRangePickerState,
onRangeConfirmation: (Long?, Long?) -> Unit
) {
if (pickerState.displayMode == DisplayMode.Picker) return
Row(modifier = Modifier.align(Alignment.End)) {
TextButton(onClick = state.onDismissClick) {
Text(text = "Cancelar")
}
TextButton(
onClick = {
state.onConfirmationClick(
pickerState.selectedStartDateMillis,
pickerState.selectedEndDateMillis
)
onRangeConfirmation(
pickerState.selectedStartDateMillis,
pickerState.selectedEndDateMillis
)
},
enabled = pickerState.selectedEndDateMillis != null
) {
Text(text = "Guardar")
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ModalButtons(
state: DateRangePickerDialogState,
pickerState: DateRangePickerState,
onRangeConfirmation: (Long?, Long?) -> Unit
) {
if (pickerState.displayMode == DisplayMode.Input) return
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, end = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
IconButton(onClick = state.onDismissClick) {
Icon(Icons.Filled.Close, contentDescription = "Cerrar")
}
TextButton(
onClick = {
state.onConfirmationClick(
pickerState.selectedStartDateMillis,
pickerState.selectedEndDateMillis
)
onRangeConfirmation(
pickerState.selectedStartDateMillis,
pickerState.selectedEndDateMillis
)
},
enabled = pickerState.selectedEndDateMillis != null
) {
Text(text = "Guardar")
}
}
}
Ahora, hemos creado el estado derivado headline
para formatear el valor de la cabeza cada que el usuario cambia la selección de una fecha. Su valor es el uso de nuestras utilidades de fechas:
@OptIn(ExperimentalMaterial3Api::class)
val DateRangePickerState.headline:String get() {
val start = selectedStartDateMillis.asInstant()?.asMonthAndDayString()?:"Inicio"
val end = selectedEndDateMillis.asInstant()?.asMonthAndDayString()?:"Fin"
return "$start - $end"
}
Mostrar Rango De Fechas
Con el diálogo de selección de rangos definido, resta añadir el Text Field.
Inicia el range date picker desde DatePickersContent()
a partir de un parámetro onRangeButtonClick
:
@Composable
fun DatePickersContent(
padding: PaddingValues,
state: DatePickersState,
onDateButtonClick: () -> Unit,
onRangeButtonClick: () -> Unit // <-
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
//...
Spacer(Modifier.size(16.dp))
Text("Date Picker con Rango")
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = state.rangeSelected,
onValueChange = {},
readOnly = true,
leadingIcon = {
IconButton(onClick = onRangeButtonClick) {
Icon(imageVector = Icons.Filled.DateRange, contentDescription = null)
}
}
)
}
}
Acto seguido, desde DatePickersScren()
y:
- Crea la instancia de
DateRangePickerDialogViewModel
- Invoca a
CustomerDateRangePickerDialog()
- Pasa a
onStart()
al parámetroonRangeConfirmed
deDatePickersContent()
@Composable
fun DatePickersScreen(
datePickerViewModel: DatePickerDialogViewModel = viewModel(),
rangePickerViewModel: DateRangePickerDialogViewModel = viewModel(), // (1)
mainContentViewModel: DatePickersViewModel = viewModel()
) {
val datePickerState by datePickerViewModel.state.collectAsState()
val rangePickerState by rangePickerViewModel.state.collectAsState()
val mainContentState by mainContentViewModel.state.collectAsState()
CustomDatePickerDialog(
state = datePickerState,
onDateSelected = mainContentState.onDateSelected
)
CustomDateRangePickerDialog(// (2)
state = rangePickerState,
onConfirmation = mainContentState.onRangeSelected
)
Scaffold(
topBar = { TopBar() },
content = { padding ->
DatePickersContent(
padding = padding,
state = mainContentState,
onDateButtonClick = datePickerState.onStart,
onRangeButtonClick = rangePickerState.onStart // (3)
)
}
)
}
Finaliza ejecutando el proyecto Android Studio, haz click en el leading icon del Text Field de rango, selecciona las fechas, confirma y visualiza el cambio.