Los DatePicker son uno de los componentes más habituales en cualquier formulario. Con él, podremos mostrar un diálogo donde el usuario podrá seleccionar fechas y gestionarlas de una forma muy sencilla.

DatePicker en Kotlin

El funcionamiento es muy sencillo, al lanzar dicho componente se mostrará en pantalla un diálogo con la opción de seleccionar un día, también podremos cambiar de año, mes e incluso limitar un rango de fechas.

Date Picker en Android
Ejemplo de un DatePicker

Estoy seguro que habrás visto alguno en cualquiera de las apps de tu dispositivo. Así que es el momento de crear el nuestro.

Let’s code

Lo primero que haremos será crear un nuevo proyecto. Yo lo llamaré DatePickerExample. Como siempre seleccionaremos una empty activity.

Una vez creado el proyecto empezaremos con nuestro layout activity_main. Seguramente al crear el proyecto te habrá puesto que el padre de la vista sea un ConstraintLayout, así que vamos a cambiarlo por LinearLayout. También añadiremos un texto para hacer un poco más bonita la vista.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="24dp"
        android:text="Reserva en Restaurante Aris"
        android:textSize="24sp"
        android:textStyle="bold" />
    
</LinearLayout>

Es un código muy sencillo, por destacar algo fijaros que he añadido un margen de 24dp a mi textView porque no quiero que se esté tan cerca de los bordes, también le he puesto un textSize de 24sp para hacerlo más grande y que sea el título de la pantalla y he terminado con un textStyle Bold para que esté en negrita y destaque del resto.

Por último tenemos que añadir un editText que será el encargado de mostrar la fecha seleccionada y que cuando pulsemos dicho componente se nos muestre el diálogo. Lo meteremos debajo del textView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="24dp"
        android:text="Reserva en Restaurante Aris"
        android:textSize="24sp"
        android:textStyle="bold" />

    <EditText
        android:id="@+id/etDate"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="24dp"
        android:clickable="false"
        android:focusable="false"
        android:hint="¿Cuándo quieres ir?"/>

</LinearLayout>

Para empezar le hemos puesto una id llamada etDate, porque necesitaremos usarlo dentro de nuestra activity. Luego tenemos dos atributos con los cuales no hemos trabajado todavía, clickable y focusable. Estos atributos nos permitirán que nuestro editText no se pueda seleccionar ni pulsar, porque si no lo ponemos, cuando el usuario lo pulse le saldrá el teclado y podrá escribir y nosotros solo queremos que se pueda pulsar para mostrar el datePicker.

Ya tenemos la parte visual terminada, pasamos a nuestra clase MainActivity,

Nuestra clase será muy sencilla. En el método onCreate(), llamaremos a nuestro editText y le pondremos un listener, para que cada vez que se pulse llame al método que muestra el datePicker.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        etDate.setOnClickListener { showDatePickerDialog() }
    }

    private fun showDatePickerDialog() {
    }

}

Esta sería por ahora nuestra clase. El siguiente paso será crear un DatePickerFragment, que será la clase encargada de crear el datePicker y avisar a nuestra MainActivity cuando el usuario ha seleccionado una fecha para poder asignarla en nuestro editText.

En el lateral de nuestro Android Studio, hacemos click en project y vamos a la ruta donde tenemos nuestra clase,

Ruta de exploración en android studio
Ruta de nuestra clase.

Lo primero de todo, asegúrate que en modo de vista project (arriba a la izquierda), para tener la misma ruta que en el ejemplo. También tienes un botón muy útil en ese menú, select open file (el círculo que está seleccionado) que lo que hace es abrir la ruta del fichero que tienes abierto.

Una vez estés ahí, haciendo click derecho en la carpeta pickerexample que contiene el fichero MainActivity, irás a new>Kotlin File/Class. Crearás una clase llamada DatePickerFragment.

creando clases en kotlin en android studio
Creando nuestra clase DatePickerFragment.

Una vez creada la clase empieza el caos, vamos a ir poco a poco porque puede ser un poco confuso.

Actualmente tenemos un código como el que vemos a continuación.

class DatePickerFragment(){
}

Se trata de una simple clase vacía. Vamos a darle un poquito de amor.

Lo primero que vamos a hacer es que esta clase reciba un listener, que va a ser una función entera que le mandamos desde nuestro MainActivity, para que pueda llamarla desde esta clase. Es decir, supongamos que nuestra clase principal (el MainActivity) tiene una función que se llama ejemploDeFuncion(), esta función solo puede ser ejecutada desde dicha clase. Nosotros queremos que esta función se llame cuando la fecha del datePicker se seleccione, pero eso se hace en la clase DatePickerFragment, así que lo que hacemos es que cuando creamos la clase del picker, le mandamos la función ejemploDeFunción() para que esta clase pueda llamarlo. Puede parecer un poco complicado de entender pero verás que es muy sencillo.

class DatePickerFragment(val listener: (day: Int, month: Int, year: Int) -> Unit)

Entonces, nuestra clase DatePickerFragment, va a recibir el listener que hemos dicho, y para poder ejecutar ese listener tenemos que mandarle 3 parámetros de tipo Int. El -> Unit del final lo podéis obviar, no es relevante.

El siguiente paso será hacer que nuestra clase DatePickerFragment, tenga las funciones necesarias para mostrar el DatePicker, para eso, esta clase tendrá que extender de otra clase que Google ha creado. No quiero meterme muy a fondo con esto porque ya es de programación orientada a objetos y es algo muy largo para explicarlo en un post, si quieres saber un poco más tienes una muy buena explicación aquí.

class DatePickerFragment(val listener: (day: Int, month: Int, year: Int) -> Unit) :
    DialogFragment(), DatePickerDialog.OnDateSetListener {

Si os fijáis en el código anterior, lo que hemos hecho ha sido añadir después de nuestro listener dos puntos, y a continuación, hemos puesto la clase DialogFragment() y DatePickerDialog.onDateSetListener.

  • DialogFragment: Se trata de la clase padre que tiene todo el código para poder mostrar los datePicker, por ello extendemos de esta clase, porque si extendemos ya tenemos todos los métodos que esta tiene.
  • DatePickerDialog.OnDateSetListener: Añadir esto hará que podamos implementar una función para saber cuando el usuario ha seleccionado una fecha y que valores ha seleccionado.

Seguro que después de añadir esto, el código te está dando un error similar a este.

Class ‘DatePickerFragment’ is not abstract and does not implement abstract member public abstract fun onDateSet(view: DatePicker!, year: Int, month: Int, dayOfMonth: Int): Unit defined in android.app.DatePickerDialog.OnDateSetListener

Este error es completamente normal porque hemos extendido de DialogFragment e implementado la función de DatePickerDialog.onDateSetListener, así que añadir los métodos que estas dos clases nos obligan.

La forma más sencilla es ponernos encima del error y nos saldrá una bombilla roja. Si hacemos click en ella nos dará las opciones para solucionar el error. Seleccionaremos Implement members y nos saldrá un recuadro con las funciones que nos añadirá, en este caso solo una. Pulsamos en OK.

Implementar métodos en kotlin con android studio
Implementando las funciones necesarias.

Nos creará la función onDataSet().

override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {

    }

Esta función se llamará cuando el usuario seleccione una fecha y nos devolverá los datos seleccionados. Entonces será en ese momento en el que tendremos que llamar al listener para mandarle esos valores a la MainActivity.

    override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) {
        listener(day, month, year)
    }

Por último en esta clase vamos a sobre escribir una función de la clase padre, es decir de DialogFragment().

 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    }

Esta función será la encargada de iniciar y mostrar el datePicker. Para crear un datePicker, la función necesitará la fecha de inicio (separada por día, mes y año), el contexto, y un listener como el que hemos implementado para saber cuando el usuario ha seleccionado una fecha.

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val c = Calendar.getInstance()
        val year = c.get(Calendar.YEAR)
        val month = c.get(Calendar.MONTH)
        val day = c.get(Calendar.DAY_OF_MONTH)
        val picker = DatePickerDialog(activity as Context, this, year, month, day)
        return picker
    }

Este sería el método completo. Lo primero que he hecho ha sido crear un Calendar, esta clase nos permite acceder a los datos de hoy de una forma sencilla. A través del objeto Calendar, puedo llamar a la función get(), y pedirle el año, el mes y el día del mes.

Luego estoy creando el datePicker, le estoy pasando el contexto, que para ello tengo que hacer activity as Context, le estoy pasando el listener, que como lo tengo implementado en la clase solo tengo que poner this y termino añadiendo el año, el mes y el día.

Ya tenemos nuestra clase terminada. Lo siguiente será volver al MainActivity. Y dentro de la función showDatePickerDialog() que teníamos vacía, llamaremos a nuestra clase DatePickerFragment para que muestre el calendario.

   private fun showDatePickerDialog() {
        val datePicker = DatePickerFragment { day, month, year -> onDateSelected(day, month, year) }
        datePicker.show(supportFragmentManager, "datePicker")
    }

La primera línea, la que he seleccionado, está creando una instancia de la clase DatePickerFragment y a continuación tiene una función entre llaves. Esos valores son los 3 valores que tienen que devolver el listener del DatePickerFragment (día, mes y año) y mandarlos a la función onDateSelected() que todavía no existe. La segunda línea accede a esa instancia y le dice que se muestre, mandando el parámetro supportFragmentManager que tiene la clase por defecto (no tenemos que hacer nada ahí) y asignarle un tag, en mi caso le he puesto datePicker.

Nos quedaría crear la función onDateSelected().

   private fun onDateSelected(day: Int, month: Int, year: Int) {
        etDate.setText("Has seleccionado el $day del $month del año $year")
    }

Este método es muy sencillo, simplemente añade un texto a nuestro editText.

Con esto ya funcionaría nuestra aplicación, ejecútala y haz click en el editText, te saldrá el datePicker y al seleccionar una fecha aparecerá escrita. Cabe destacar que si pulsas en el año, el datePicker cambiará para seleccionar un año de una forma más sencilla.

Seleccionador de años datepicker en kotlin
Selección de años desde el datePicker.

Cambiando colores en el DatePicker

En este momento nuestra app ya funciona, pero ahora queremos personalizarlo un poco. Si nos fijamos todo el calendario gira en torno a un color. Este color lo podemos definir nosotros.

Vamos a ir al menú lateral del proyecto y vamos a buscar el fichero styles.xml. Puedes encontrarlo en app>src>main>res>values>styles.xml.

En este fichero se definen los themes y estilos de nuestra app. Creando un theme, podemos hacer que nuestra app o alguno de nuestros componentes tengan un estilo distinto y podamos reutilizarlo por toda la app, así si luego queremos cambiar un color, si hacemos un cambio en el theme, se cambiarán todos los componentes que lo usan.

    <style name="DatePickerTheme" parent="android:Theme.Material.Light.Dialog">
        <item name="android:colorAccent">#FFA726</item>
    </style>

Sin entrar mucho en el detalle ya que necesitaríamos un capítulo completo, hemos definido un estilo que su padre, o lo que es lo mismo, extiendo de android:Theme.Material.Light.Dialog, que es el theme original que define el color de los datePicker, así que básicamente lo que estamos haciendo es sobre escribir ese theme modificando la propiedad que queramos, que en este caso sería colorAccent.

Ahora iremos a DatePickerFragment y en el método onCreateDialog(), cuando creamos el picker, añadiremos un parámetro extra que será el estilo.

Así que cambiamos esto.

        val picker = DatePickerDialog(activity as Context, this, year, month, day)

Por esto.

        val picker = DatePickerDialog(activity as Context, R.style.DatePickerTheme, this, year, month, day)

Ejecutamos y al mostrar el datePicker, veremos que ha cambiado de color.

Cambiar color datepicker
DatePicker con nuestro estilo.

Si quieres saber más sobre estilos, puedes echar un vistazo al capítulo 24 – Material Design y estilos en Kotlin.

Limitando fechas en DatePicker

Supongamos que queremos usar este componente para un registro, no deberíamos dejar que seleccione un año posterior al actual para poner su fecha de nacimiento. O por ejemplo si queremos usarlo para reservar en un restaurante, no tendría sentido que reserve en una fecha pasada.

Para evitar este tipo de acciones, datePicker nos da la posibilidad de añadir una fecha mínima y otra máxima. Para ello iremos a onCreateDialog() y antes de retornar el objeto picker, vamos a trabajar con él.

Nuestro objeto picker, es una instancia de un DatePickerDialog(), cada una de estas instancias tiene un atributo dentro que es el propio picker. Para acceder a él tendremos que llamar a datePicker dentro de nuestro objeto picker.

        val picker = DatePickerDialog(activity as Context, R.style.DatePickerTheme, this, year, month, day)
        picker.datePicker

Una ves hemos accedido al datePicker, tenemos una serie de atributos. Para este caso usaremos dos.

  • maxDate: Nos permite añadir la fecha máxima, si por ejemplo ponemos el 2 de mayo del 2010, no se podrá seleccionar una fecha posterior, se verá deshabilitado.
  • minDate: Lo mismo que el atributo anterior pero con fechas anteriores.

Estas fechas que le añadimos, la mínima y la máxima (no es necesaria poner ambas, puedes poner una de las dos solamente si lo quieres), se las tenemos que asignar en milisegundos. Para acceder a los milisegundos es muy sencillo, podemos coger nuestro objeto calendario y llamar a la función timeInMilis().

El problema es que el calendario actual tiene la fecha del día de hoy, así que debemos modificarlo.

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val c = Calendar.getInstance()
        val year = c.get(Calendar.YEAR)
        val month = c.get(Calendar.MONTH)
        val day = c.get(Calendar.DAY_OF_MONTH)
        val picker = DatePickerDialog(activity as Context, R.style.DatePickerTheme, this, year, month, day)
        c.add(Calendar.YEAR, -2)
        picker.datePicker.maxDate = c.timeInMillis
        return picker
    }

Fijaros en el código anterior. Una vez creado el picker, al cual le asignamos el day, month y year. Podemos volver a modificar ese calendario con la función add(), en ella le decimos lo que queremos modificar, por ejemplo los años y ponemos -2, que quiere decir que le reste dos años a la fecha que tiene el calendario.

Luego solo tenemos que asignar el maxDate a el calendario pasándolo a milisegundos con la función timeInMilis. Si ejecutamos veremos que hay una fecha máxima que no podremos sobrepasar.

date picker fecha maxima limitada
Limitación de maxDate en nuestro DatePicker.

Supongamos por ejemplo que gestionamos las reservas de un restaurante y solo queremos que puedan reservar desde hoy hasta dentro de dos meses.

  override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val c = Calendar.getInstance()
        val year = c.get(Calendar.YEAR)
        val month = c.get(Calendar.MONTH)
        val day = c.get(Calendar.DAY_OF_MONTH)
        val picker = DatePickerDialog(activity as Context, R.style.DatePickerTheme, this, year, month, day)
        picker.datePicker.minDate = c.timeInMillis
        c.add(Calendar.MONTH, +2)
        picker.datePicker.maxDate = c.timeInMillis
        return picker
    }

Lo que hemos hecho ha sido poner de fecha mínima hoy, luego hemos sumado dos meses al mes del calendario y le decimos que se lo ponga a la fecha máxima.

Y hasta aquí el capítulo de hoy, si quieres ver como quedó tienes el ejemplo completo en el vídeo del capítulo.