Aspectos básicos de Kotlin para Android 06.2: Corrutinas y Room

Este codelab es parte del curso Conceptos básicos de Kotlin para Android. Aprovecharás al máximo este curso si trabajas con los codelabs en secuencia. Todos los codelabs del curso se detallan en la página de destino de codelabs sobre los aspectos básicos de Kotlin para Android.

Introducción

Una de las principales prioridades para crear una experiencia del usuario fluida con tu app es asegurarte de que la IU siempre esté responsiva y se ejecute sin problemas. Una forma de mejorar el rendimiento de la IU es colocar en segundo plano las tareas de larga duración, como las operaciones de bases de datos.

En este codelab, implementarás la parte orientada a los usuarios de la app TrackMySleepQuality mediante corrutinas de Kotlin para realizar operaciones de bases de datos fuera del subproceso principal.

Conocimientos que ya deberías tener

Debes estar familiarizado con lo siguiente:

  • Compilar una interfaz de usuario básica (IU) mediante una actividad, fragmentos, vistas y controladores de clics
  • Navegar entre fragmentos y usar safeArgs para pasar datos simples entre fragmentos
  • Mira modelos, fábricas de modelos, transformaciones y LiveData.
  • Cómo crear una base de datos Room, crear un DAO y definir entidades
  • Resulta útil si estás familiarizado con los conceptos de subprocesos y multiprocesamiento.

Qué aprenderás

  • Cómo funcionan los subprocesos en Android
  • Cómo usar las corrutinas de Kotlin para alejar las operaciones de la base de datos del subproceso principal
  • Cómo mostrar datos con formato en un TextView

Actividades

  • Extiende la app TrackMySleepQuality para recopilar, almacenar y mostrar datos en la base de datos y desde ella.
  • Usa corrutinas para ejecutar operaciones de bases de datos de larga duración en segundo plano.
  • Usa LiveData para activar la navegación y la visualización de una barra de notificaciones.
  • Usa LiveData para habilitar e inhabilitar botones.

En este codelab, compilarás el modelo de vista, las corrutinas y las partes de la visualización de datos de la app TrackMySleepQuality.

La app tiene dos pantallas, representadas por fragmentos, como se muestra en la siguiente figura.

La primera pantalla, que se muestra a la izquierda, tiene botones para iniciar y detener el seguimiento. La pantalla muestra todos los datos de sueño del usuario. El botón Borrar borra de forma permanente todos los datos que la app recopiló para el usuario.

En la segunda pantalla, que se muestra a la derecha, se selecciona una calificación de calidad del sueño. En la app, la calificación se representa numéricamente. Para fines de desarrollo, la app muestra los íconos de rostro y sus equivalentes numéricos.

El flujo del usuario es el siguiente:

  • El usuario abre la app y aparece la pantalla de seguimiento del sueño.
  • El usuario presiona el botón Iniciar. Esto registra la hora de inicio y la muestra. El botón Iniciar está inhabilitado y está habilitado el botón Detener.
  • El usuario presiona el botón Detener. Se grabará la hora de finalización y se abrirá la pantalla de calidad del sueño.
  • El usuario selecciona un ícono de calidad del sueño. La pantalla se cerrará, y la pantalla de seguimiento mostrará la hora de finalización y la calidad del sueño. El botón Detener está inhabilitado y habilitado el botón Iniciar. La app está lista para otra noche.
  • Se habilita el botón Borrar cuando la base de datos tiene datos. Cuando el usuario presiona el botón Borrar, se borran todos sus datos sin recursos, y no se muestra el mensaje "¿Estás seguro?".

Esta app usa una arquitectura simplificada, como se muestra a continuación en el contexto de la arquitectura completa. La app usa solo los siguientes componentes:

  • Controlador de IU
  • Ver el modelo y LiveData
  • Una base de datos de Room

En esta tarea, usarás un elemento TextView para mostrar datos de seguimiento del sueño con formato. (Esta no es la interfaz final. Aprenderás de otra manera en otro codelab".

Puedes continuar con la app TrackMySleepQuality que compilaste en el codelab anterior o descargar la app inicial para este codelab.

Paso 1: Descarga y ejecuta la app de inicio

  1. Descarga la app TrackMySleepQuality-Coroutines-Starter desde GitHub.
  2. Compila y ejecuta la app. La app muestra la IU para el fragmento SleepTrackerFragment, pero no los datos. Los botones no responden a toques.

Paso 2: Inspecciona el código

El código de inicio para este codelab es el mismo que el código de la solución para el codelab de creación de bases de datos de Room 6.1.

  1. Abre res/layout/activity_main.xml. Este diseño contiene el fragmento nav_host_fragment. Además, observa la etiqueta <merge>.

    La etiqueta merge se puede usar para eliminar los diseños redundantes cuando se incluyen diseños, por lo que se recomienda usarla. Un ejemplo de un diseño redundante sería ConstraintLayout > LinearLayout > TextView, en el que el sistema podría eliminar el LinearLayout. Este tipo de optimización puede simplificar la jerarquía de vistas y mejorar el rendimiento de la app.
  2. En la carpeta navigation, abre navigation.xml. Puedes ver dos fragmentos y las acciones de navegación que los conectan.
  3. En la carpeta layout, haz doble clic en el fragmento del seguimiento de sueño para ver su diseño XML. Ten en cuenta lo siguiente:
  • Los datos de diseño se unen en un elemento <layout> para habilitar la vinculación de datos.
  • ConstraintLayout y las otras vistas están organizadas dentro del elemento <layout>.
  • El archivo tiene una etiqueta de marcador de posición <data>.

La app de inicio también proporciona dimensiones, colores y estilos para la IU. La app contiene una base de datos Room, un DAO y una entidad SleepNight. Si no completaste el codelab anterior, asegúrate de explorar estos aspectos del código por tu cuenta.

Ahora que tienes una base de datos y una IU, debes recopilar datos, agregarlos y mostrarlos. Todo este trabajo se realiza en el modelo de vista. Tu modelo de vista del monitor de sueño controlará los clics en botones, interactuará con la base de datos a través del DAO y proporcionará datos a la IU a través de LiveData. Todas las operaciones de la base de datos se deberán ejecutar desde el subproceso de IU principal. Para ello, deberás usar corrutinas.

Paso 1: Agrega SleepTrackerViewModel

  1. En el paquete sleeptracker, abre SleepTrackerViewModel.kt.
  2. Inspecciona la clase SleepTrackerViewModel, que se proporciona en la app de inicio y también se muestra a continuación. Ten en cuenta que la clase extiende AndroidViewModel(). Esta clase es la misma que ViewModel, pero toma el contexto de la aplicación como parámetro y lo pone a disposición como propiedad. La necesitarás más tarde.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

Paso 2: Agrega SleepTrackerViewModelFactory

  1. En el paquete sleeptracker, abre SleepTrackerViewModelFactory.kt.
  2. Revisa el código que se te proporcionó para la fábrica, que se muestra a continuación:
class SleepTrackerViewModelFactory(
       private val dataSource: SleepDatabaseDao,
       private val application: Application) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
           return SleepTrackerViewModel(dataSource, application) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

Observe lo siguiente:

  • El SleepTrackerViewModelFactory proporcionado toma el mismo argumento que el ViewModel y extiende ViewModelProvider.Factory.
  • Dentro de la fábrica, el código anula create(), que toma cualquier tipo de clase como argumento y muestra un ViewModel.
  • En el cuerpo de create(), el código verifica que haya una clase SleepTrackerViewModel disponible y, si la hay, muestra una instancia de ella. De lo contrario, el código arrojará una excepción.

Paso 3: Actualiza SleepTrackerFragment

  1. En SleepTrackerFragment, obtén una referencia al contexto de la aplicación. Coloca la referencia en onCreateView(), debajo de binding. Necesitas una referencia a la app a la que está vinculado este fragmento para pasar al proveedor de fábrica del modelo de vista.

    La función requireNotNull de Kotlin arroja una IllegalArgumentException si el valor es null.
val application = requireNotNull(this.activity).application
  1. Necesitas una referencia a tu fuente de datos a través de una referencia al DAO. En onCreateView(), antes de return, define un elemento dataSource. Para obtener una referencia al DAO de la base de datos, usa SleepDatabase.getInstance(application).sleepDatabaseDao.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. En onCreateView(), antes de return, crea una instancia de viewModelFactory. Debes pasarle dataSource y application.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. Ahora que tienes una fábrica, obtén una referencia al SleepTrackerViewModel. El parámetro SleepTrackerViewModel::class.java hace referencia a la clase Java del entorno de ejecución de este objeto.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. El código finalizado debería verse así:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)

Este es el método onCreateView() hasta ahora:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_tracker, container, false)

        val application = requireNotNull(this.activity).application

        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao

        val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

        val sleepTrackerViewModel =
                ViewModelProviders.of(
                        this, viewModelFactory).get(SleepTrackerViewModel::class.java)

        return binding.root
    }

Paso 4: Agrega la vinculación de datos para el modelo de vista

Con el ViewModel básico implementado, debes terminar de configurar la vinculación de datos en el SleepTrackerFragment para conectar el ViewModel con la IU.


En el archivo de diseño fragment_sleep_tracker.xml:

  1. Dentro del bloque <data>, crea un <variable> que haga referencia a la clase SleepTrackerViewModel.
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

En SleepTrackerFragment:

  1. Establece la actividad actual como el propietario del ciclo de vida de la vinculación. Agrega este código al método onCreateView(), antes de la sentencia return:
binding.setLifecycleOwner(this)
  1. Asigna la variable de vinculación sleepTrackerViewModel a sleepTrackerViewModel. Coloca este código dentro de onCreateView(), debajo del código que crea SleepTrackerViewModel:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. Es probable que vea un error, ya que deberá volver a crear el objeto de vinculación. Limpia y vuelve a compilar el proyecto para deshacerte del error.
  2. Finalmente, como siempre, asegúrese de que su código se compile y se ejecute sin errores.

En Kotlin, las corrutinas son la forma de controlar tareas de larga duración de forma elegante y eficiente. Las corrutinas de Kotlin te permiten convertir en código secuencial el código basado en devoluciones de llamada. Por lo general, el código escrito de forma secuencial es más fácil de leer y hasta puede usar funciones de lenguaje como excepciones. Al final, las corrutinas y las devoluciones de llamada hacen lo mismo: esperan hasta que un resultado esté disponible desde una tarea de larga duración y continúan su ejecución.

Las corrutinas tienen las siguientes propiedades:

  • Las corrutinas son asíncronas y no provocan bloqueos.
  • Las corrutinas usan funciones de suspend para hacer que el código asíncrono sea secuencial.

Las corrutinas son asíncronas.

Una corrutina se ejecuta de forma independiente de los pasos de ejecución principales del programa. Puede ser en paralelo o en otro procesador. También es posible que, mientras el resto de la app espera la entrada, te encuentres un poco en procesamiento. Uno de los aspectos importantes de la asíncrona es que no puedes esperar que el resultado esté disponible, hasta que lo esperes explícitamente.

Por ejemplo, supongamos que tienes una pregunta que requiere investigación y le pides a un colega que busque la respuesta. Se inician y trabajan en ella; es como si estuvieran haciendo el trabajo de manera asíncrona y en un subproceso independiente. Puedes continuar con otro trabajo que no dependa de la respuesta, hasta que tu colega regrese y te diga cuál es la respuesta.

Las corrutinas no tienen bloqueo.

Sin bloqueo significa que una corrutina no bloquea el subproceso principal o de IU. Por lo tanto, con las corrutinas, los usuarios siempre tienen la mejor experiencia posible, ya que la interacción de la IU siempre tiene prioridad.

Las corrutinas usan funciones de suspensión para hacer que el código asíncrono sea secuencial.

La palabra clave suspend es la forma en que Kotlin marca una función (o tipo de función) como disponible para las corrutinas. Cuando una corrutina llama a una función marcada con suspend, en lugar de bloquearse hasta que se muestra como una llamada a función normal, la corrutina suspende la ejecución hasta que el resultado esté listo. Luego, la corrutina se reanuda donde la dejó, con el resultado.

Mientras la corrutina está suspendida y a la espera del resultado, desbloquea el subproceso en el que se ejecuta. De esa manera, se pueden ejecutar otras funciones o corrutinas.

La palabra clave suspend no especifica el subproceso en el que se ejecuta el código. Una función de suspensión puede ejecutarse en un subproceso en segundo plano o en el subproceso principal.

Para usar corrutinas en Kotlin, necesitas tres elementos:

  • Un trabajo
  • Un despachador
  • Un permiso

Trabajo: Básicamente, un trabajo es cualquier cosa que se pueda cancelar. Cada corrutina tiene un trabajo que puedes usar para cancelarla. Los trabajos se pueden organizar en jerarquías de elementos superiores y secundarios. Si cancelas un trabajo superior, se cancelan de inmediato todos los elementos secundarios, lo que es mucho más conveniente que cancelar cada corrutina de forma manual.

Dispatcher: el despachador envía corrutinas para ejecutarse en varios subprocesos. Por ejemplo, Dispatcher.Main ejecuta tareas en el subproceso principal y Dispatcher.IO descarga las tareas de E/S de bloqueo en un grupo compartido de subprocesos.

Alcance: El alcance de una corrutina define el contexto en el que se ejecuta la corrutina. Un permiso combina información sobre un trabajo y un despachador de una corrutina. Los alcances hacen un seguimiento de las corrutinas. Cuando inicias una corrutina, está dentro de un alcance, lo que significa que indicaste qué alcance hará un seguimiento de la corrutina.

Si quieres que el usuario pueda interactuar con los datos de sueño, sigue estos pasos:

  • Cuando el usuario presiona el botón Iniciar, la app crea una nueva noche de sueño y la almacena en la base de datos.
  • Cuando el usuario presiona el botón Detener, la app actualiza la noche con una hora de finalización.
  • Cuando el usuario presiona el botón Borrar, la app borra los datos de la base de datos.

Estas operaciones de base de datos pueden llevar mucho tiempo, por lo que deberían ejecutarse en un subproceso independiente.

Paso 1: Configura corrutinas para operaciones de bases de datos

Cuando se presiona el botón Iniciar en la app de monitor de sueño, debes llamar a una función en SleepTrackerViewModel para crear una nueva instancia de SleepNight y almacenarla en la base de datos.

Cuando se presiona cualquiera de los botones, se activa una operación de base de datos, como crear o actualizar un SleepNight. Por este y otros motivos, usas corrutinas a fin de implementar controladores de clics para los botones de la app.

  1. Abre el archivo build.gradle a nivel de la app y busca las dependencias para las corrutinas. Para usar corrutinas, necesitas estas dependencias que se agregaron automáticamente.

    El objeto $coroutine_version se define en el archivo build.gradle del proyecto como coroutine_version = '1.0.0'.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. Abre el archivo SleepTrackerViewModel.
  2. En el cuerpo de la clase, define viewModelJob y asígnale una instancia de Job. Esta viewModelJob te permite cancelar todas las corrutinas que inició este modelo de vista cuando ya no se usa y se destruye. De esta manera, no terminarás con corrutinas que no tengan dónde regresar.
private var viewModelJob = Job()
  1. Al final del cuerpo de la clase, anula onCleared() y cancela todas las corrutinas. Cuando se destruye el ViewModel, se llama a onCleared().
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Debajo de la definición de viewModelJob, define un elemento uiScope para las corrutinas. El alcance determina en qué subproceso se ejecutará la corrutina y también necesita conocer el trabajo. Para obtener un permiso, solicite una instancia de CoroutineScope y pase un despachador y un trabajo.

Si usas Dispatchers.Main, las corrutinas iniciadas en uiScope se ejecutarán en el subproceso principal. Esto es razonable para muchas corrutinas iniciadas por ViewModel, ya que, después de que estas corrutinas realizan algún procesamiento, generan una actualización de la IU.

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. Debajo de la definición de uiScope, define una variable llamada tonight para conservar la noche actual. Crea la variable MutableLiveData, ya que deberás poder observar los datos y cambiarlos.
private var tonight = MutableLiveData<SleepNight?>()
  1. Para inicializar la variable tonight lo antes posible, crea un bloque init debajo de la definición de tonight y llama a initializeTonight(). Debes definir initializeTonight() en el siguiente paso.
init {
   initializeTonight()
}
  1. Debajo del bloque init, implementa initializeTonight(). En uiScope, inicia una corrutina. Adentro, obtén el valor de tonight de la base de datos mediante una llamada a getTonightFromDatabase() y asigna el valor a tonight.value. Debes definir getTonightFromDatabase() en el siguiente paso.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. Implementar getTonightFromDatabase() Defínelo como una función private suspend que muestre un SleepNight anulable si no hay un SleepNight iniciado actualmente. Esto genera un error, ya que la función debe mostrar algo.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. Dentro del cuerpo de la función getTonightFromDatabase(), muestra el resultado de una corrutina que se ejecuta en el contexto Dispatchers.IO. Usa el despachador de E/S, ya que obtener datos de la base de datos es una operación de E/S y no tiene nada que ver con la IU.
  return withContext(Dispatchers.IO) {}
  1. Dentro del bloque de retorno, permite que la corrutina llegue esta noche (la más reciente) desde la base de datos. Si las horas de inicio y finalización no son las mismas, lo que significa que la noche ya se completó, muestra null. De lo contrario, devuelve la noche.
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

La función de suspensión de getTonightFromDatabase() completada debería verse de la siguiente manera: No debería haber más errores.

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

Paso 2: Agrega el controlador de clics para el botón Inicio

Ahora puedes implementar onStartTracking(), el controlador de clics del botón Start. Debes crear un nuevo SleepNight, insertarlo en la base de datos y asignarlo a tonight. La estructura de onStartTracking() se verá muy similar a initializeTonight().

  1. Comienza con la definición de función para onStartTracking(). Puedes colocar los controladores de clics por encima de onCleared() en el archivo SleepTrackerViewModel.
fun onStartTracking() {}
  1. Dentro de onStartTracking(), inicia una corrutina en el uiScope, ya que necesitarás este resultado para continuar y actualizar la IU.
uiScope.launch {}
  1. Dentro del lanzamiento de la corrutina, crea un nuevo SleepNight, que captura la hora actual como la hora de inicio.
        val newNight = SleepNight()
  1. Dentro del lanzamiento de la corrutina, llama a insert() para insertar newNight en la base de datos. Verás un error porque aún no definiste esta función de suspensión insert(). (Esta no es la función DAO con el mismo nombre).
       insert(newNight)
  1. Además, dentro del lanzamiento de la corrutina, actualiza tonight.
       tonight.value = getTonightFromDatabase()
  1. Debajo de onStartTracking(), define insert() como una función private suspend que toma un objeto SleepNight como su argumento.
private suspend fun insert(night: SleepNight) {}
  1. Para el cuerpo de insert(), inicia una corrutina en el contexto de E/S e inserta la noche en la base de datos llamando a insert() desde el DAO.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. En el archivo de diseño fragment_sleep_tracker.xml, agrega el controlador de clics para onStartTracking() a start_button con la magia de la vinculación de datos que configuraste antes. La notación de funciones @{() -> crea una función lambda que no toma argumentos y llama al controlador de clics en el sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. Compila y ejecuta la app. Presiona el botón Iniciar. Esta acción crea datos, pero aún no puedes ver nada. Eso lo solucionarás más tarde.
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

Paso 3: Muestra los datos

En SleepTrackerViewModel, la variable nights hace referencia a LiveData porque getAllNights() en el DAO muestra LiveData.

Es una función Room que, cada vez que cambian los datos de la base de datos, se actualiza LiveData nights para mostrar los datos más recientes. Nunca es necesario establecer explícitamente LiveData ni actualizarlo. Room actualiza los datos para que coincidan con la base de datos.

Sin embargo, si muestras nights en una vista de texto, se mostrará la referencia del objeto. Para ver el contenido del objeto, transforma los datos en una string con formato. Usa un mapa Transformation que se ejecute cada vez que nights reciba datos nuevos de la base de datos.

  1. Abre el archivo Util.kt y quita el comentario del código para la definición de formatNights() y las declaraciones import asociadas. Para quitar el comentario de un código en Android Studio, selecciona todo el que esté marcado con // y presiona Cmd+/ o Control+/.
  2. Ten en cuenta que formatNights() muestra un tipo Spanned, que es una string con formato HTML.
  3. Abre strings.xml. Observa el uso de CDATA para dar formato a los recursos de strings a fin de mostrar los datos de sueño.
  4. Abre SleepTrackerViewModel. En la clase SleepTrackerViewModel, debajo de la definición de uiScope, define una variable llamada nights. Obtén todas las noches de la base de datos y asígnalas a la variable nights.
private val nights = database.getAllNights()
  1. Debajo de la definición de nights, agrega código para transformar nights en nightsString. Usa la función formatNights() de Util.kt.

    Pasa nights a la función map() de la clase Transformations. Para obtener acceso a los recursos de strings, define la función de mapeo como llamando a formatNights(). Proporciona nights y un objeto Resources.
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. Abre el archivo de diseño fragment_sleep_tracker.xml. En la propiedad android:text, en TextView, puedes reemplazar la string del recurso por una referencia a nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. Vuelve a compilar el código y ejecuta la app. Ahora se deberían mostrar todos los datos de sueño con las horas de inicio.
  2. Presiona el botón Iniciar algunas veces más y verás más datos.

En el siguiente paso, habilitarás la funcionalidad del botón Stop.

Paso 4: Agrega el controlador de clics para el botón Detener

Con el mismo patrón del paso anterior, implementa el controlador de clics para el botón Stop en SleepTrackerViewModel..

  1. Agrega onStopTracking() a ViewModel. Inicia una corrutina en el uiScope. Si aún no se establece la hora de finalización, establece endTimeMilli en la hora actual del sistema y llama a update() con los datos nocturnos.

    En Kotlin, la sintaxis return@label especifica la función que muestra esta sentencia, entre varias funciones anidadas.
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. Implementa update() con el mismo patrón que usaste para implementar insert().
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. Para conectar el controlador de clics a la IU, abre el archivo de diseño fragment_sleep_tracker.xml y agrega el controlador de clics a stop_button.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. Compila y ejecuta tu app.
  2. Presiona Iniciar y, luego, Detener. Verás la hora de inicio, la de finalización y la calidad del sueño sin valores, y la hora que dormiste.

Paso 5: Agrega el controlador de clics para el botón Borrar

  1. Del mismo modo, implementa onClear() y clear().
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. Para conectar el controlador de clics a la IU, abre fragment_sleep_tracker.xml y agrega el controlador de clics a clear_button.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. Compila y ejecuta tu app.
  2. Presiona Borrar para deshacerte de todos los datos. Luego, presiona Iniciar y Detener para crear datos nuevos.

Proyecto de Android Studio: TrackMySleepQualityCoroutines

  • Usa ViewModel, ViewModelFactory y la vinculación de datos a fin de configurar la arquitectura de la IU para la app.
  • Para que la IU se siga ejecutando sin problemas, usa corrutinas en las tareas de larga duración, como todas las operaciones de bases de datos.
  • Las corrutinas son asíncronas y no provocan bloqueos. Usan funciones suspend para hacer que el código asíncrono sea secuencial.
  • Cuando una corrutina llama a una función marcada con suspend, en lugar de bloquearse hasta que se muestre esa función como una normal, se suspende la ejecución hasta que el resultado esté listo. Luego, reanuda la actividad desde donde la dejó.
  • La diferencia entre bloquear y suspender es que, si un subproceso está bloqueado, no se realiza ningún otro trabajo. Si la conversación se suspende, se producirá otro trabajo hasta que el resultado esté disponible.

Para iniciar una corrutina, necesitas un trabajo, un despachador y un alcance:

  • Básicamente, un trabajo es cualquier cosa que se pueda cancelar. Todas las corrutinas tienen un trabajo, y puedes usarlas para cancelar una corrutina.
  • El despachador envía corrutinas para ejecutarse en varios subprocesos. Dispatcher.Main ejecuta tareas en el subproceso principal y Dispartcher.IO permite descargar tareas de bloqueo de E/S en un grupo compartido de subprocesos.
  • El alcance combina información, incluido un trabajo y un despachador, para definir el contexto en el que se ejecuta la corrutina. Los alcances hacen un seguimiento de las corrutinas.

Para implementar controladores de clics que activan las operaciones de la base de datos, sigue este patrón:

  1. Inicia una corrutina que se ejecute en el subproceso principal o de la IU, ya que el resultado afecta a la IU.
  2. Llama a una función de suspensión para realizar la tarea prolongada de modo que no bloquees el subproceso de IU mientras esperas el resultado.
  3. El trabajo de larga duración no tiene nada que ver con la IU, por lo que debes cambiar al contexto de E/S. De esa manera, el trabajo puede ejecutarse en un conjunto de subprocesos optimizado y reservado para este tipo de operaciones.
  4. Luego, llame a la función de la base de datos para hacer el trabajo.

Usa un mapa Transformations para crear una string a partir de un objeto LiveData cada vez que el objeto cambie.

Curso de Udacity:

Documentación para desarrolladores de Android:

Otros documentos y artículos:

En esta sección, se enumeran las posibles tareas para los alumnos que trabajan con este codelab como parte de un curso que dicta un instructor. Depende del instructor hacer lo siguiente:

  • Si es necesario, asigna la tarea.
  • Informa a los alumnos cómo enviar los deberes.
  • Califica las tareas.

Los instructores pueden usar estas sugerencias lo poco o lo que quieran, y deben asignar cualquier otra tarea que consideren apropiada.

Si estás trabajando en este codelab por tu cuenta, usa estas tareas para poner a prueba tus conocimientos.

Responde estas preguntas

Pregunta 1

¿Cuáles de las siguientes opciones son ventajas de las corrutinas?

  • No provocan un bloqueo.
  • Se ejecutan de forma asíncrona.
  • Se pueden ejecutar en un subproceso diferente del principal.
  • Siempre hacen que las apps se ejecuten más rápido.
  • Pueden usar excepciones.
  • Se pueden escribir y leer como código lineal.

Pregunta 2

¿Qué es una función de suspensión?

  • Una función común anotada con la palabra clave suspend.
  • Una función a la que se puede llamar dentro de corrutinas.
  • Mientras se ejecuta una función de suspensión, el subproceso de llamada permanece suspendido.
  • Las funciones de suspensión deben ejecutarse siempre en segundo plano.

Pregunta 3

¿Cuál es la diferencia entre bloquear y suspender un subproceso? Marque todas las opciones verdaderas.

  • Cuando se bloquea la ejecución, no se puede ejecutar ningún otro trabajo en el subproceso bloqueado.
  • Cuando se suspende la ejecución, el subproceso puede realizar otros trabajos mientras se completa el trabajo descargado.
  • La suspensión es más eficaz, ya que es posible que los subprocesos no estén esperando y no hagan nada.
  • Ya sea que esté bloqueado o suspendido, la ejecución todavía espera el resultado de la corrutina antes de continuar.

Comienza con la siguiente lección: 6.3 Usa LiveData para controlar los estados del botón

Para ver vínculos a otros codelabs de este curso, consulta la página de destino de codelabs sobre aspectos básicos de Kotlin para Android.