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
- Descarga la app TrackMySleepQuality-Coroutines-Starter desde GitHub.
- 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.
- Abre res/layout/activity_main.xml. Este diseño contiene el fragmento
nav_host_fragment
. Además, observa la etiqueta<merge>
.
La etiquetamerge
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. - En la carpeta navigation, abre navigation.xml. Puedes ver dos fragmentos y las acciones de navegación que los conectan.
- 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
- En el paquete sleeptracker, abre SleepTrackerViewModel.kt.
- 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 extiendeAndroidViewModel()
. Esta clase es la misma queViewModel
, 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
- En el paquete sleeptracker, abre SleepTrackerViewModelFactory.kt.
- 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 elViewModel
y extiendeViewModelProvider.Factory
. - Dentro de la fábrica, el código anula
create()
, que toma cualquier tipo de clase como argumento y muestra unViewModel
. - En el cuerpo de
create()
, el código verifica que haya una claseSleepTrackerViewModel
disponible y, si la hay, muestra una instancia de ella. De lo contrario, el código arrojará una excepción.
Paso 3: Actualiza SleepTrackerFragment
- En
SleepTrackerFragment
, obtén una referencia al contexto de la aplicación. Coloca la referencia enonCreateView()
, debajo debinding
. 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ónrequireNotNull
de Kotlin arroja unaIllegalArgumentException
si el valor esnull
.
val application = requireNotNull(this.activity).application
- Necesitas una referencia a tu fuente de datos a través de una referencia al DAO. En
onCreateView()
, antes dereturn
, define un elementodataSource
. Para obtener una referencia al DAO de la base de datos, usaSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- En
onCreateView()
, antes dereturn
, crea una instancia deviewModelFactory
. Debes pasarledataSource
yapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Ahora que tienes una fábrica, obtén una referencia al
SleepTrackerViewModel
. El parámetroSleepTrackerViewModel::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)
- 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
:
- Dentro del bloque
<data>
, crea un<variable>
que haga referencia a la claseSleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
En SleepTrackerFragment
:
- 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 sentenciareturn
:
binding.setLifecycleOwner(this)
- Asigna la variable de vinculación
sleepTrackerViewModel
asleepTrackerViewModel
. Coloca este código dentro deonCreateView()
, debajo del código que creaSleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- 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.
- 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.
- 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 archivobuild.gradle
del proyecto comocoroutine_version =
'1.0.0'
.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
- Abre el archivo
SleepTrackerViewModel
. - En el cuerpo de la clase, define
viewModelJob
y asígnale una instancia deJob
. EstaviewModelJob
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()
- Al final del cuerpo de la clase, anula
onCleared()
y cancela todas las corrutinas. Cuando se destruye elViewModel
, se llama aonCleared()
.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Debajo de la definición de
viewModelJob
, define un elementouiScope
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 deCoroutineScope
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)
- Debajo de la definición de
uiScope
, define una variable llamadatonight
para conservar la noche actual. Crea la variableMutableLiveData
, ya que deberás poder observar los datos y cambiarlos.
private var tonight = MutableLiveData<SleepNight?>()
- Para inicializar la variable
tonight
lo antes posible, crea un bloqueinit
debajo de la definición detonight
y llama ainitializeTonight()
. Debes definirinitializeTonight()
en el siguiente paso.
init {
initializeTonight()
}
- Debajo del bloque
init
, implementainitializeTonight()
. EnuiScope
, inicia una corrutina. Adentro, obtén el valor detonight
de la base de datos mediante una llamada agetTonightFromDatabase()
y asigna el valor atonight.value
. Debes definirgetTonightFromDatabase()
en el siguiente paso.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- Implementar
getTonightFromDatabase()
Defínelo como una funciónprivate suspend
que muestre unSleepNight
anulable si no hay unSleepNight
iniciado actualmente. Esto genera un error, ya que la función debe mostrar algo.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- Dentro del cuerpo de la función
getTonightFromDatabase()
, muestra el resultado de una corrutina que se ejecuta en el contextoDispatchers.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) {}
- 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()
.
- Comienza con la definición de función para
onStartTracking()
. Puedes colocar los controladores de clics por encima deonCleared()
en el archivoSleepTrackerViewModel
.
fun onStartTracking() {}
- Dentro de
onStartTracking()
, inicia una corrutina en eluiScope
, ya que necesitarás este resultado para continuar y actualizar la IU.
uiScope.launch {}
- Dentro del lanzamiento de la corrutina, crea un nuevo
SleepNight
, que captura la hora actual como la hora de inicio.
val newNight = SleepNight()
- Dentro del lanzamiento de la corrutina, llama a
insert()
para insertarnewNight
en la base de datos. Verás un error porque aún no definiste esta función de suspensióninsert()
. (Esta no es la función DAO con el mismo nombre).
insert(newNight)
- Además, dentro del lanzamiento de la corrutina, actualiza
tonight
.
tonight.value = getTonightFromDatabase()
- Debajo de
onStartTracking()
, defineinsert()
como una funciónprivate suspend
que toma un objetoSleepNight
como su argumento.
private suspend fun insert(night: SleepNight) {}
- Para el cuerpo de
insert()
, inicia una corrutina en el contexto de E/S e inserta la noche en la base de datos llamando ainsert()
desde el DAO.
withContext(Dispatchers.IO) {
database.insert(night)
}
- En el archivo de diseño
fragment_sleep_tracker.xml
, agrega el controlador de clics paraonStartTracking()
astart_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 elsleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- 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.
- Abre el archivo
Util.kt
y quita el comentario del código para la definición deformatNights()
y las declaracionesimport
asociadas. Para quitar el comentario de un código en Android Studio, selecciona todo el que esté marcado con//
y presionaCmd+/
oControl+/
. - Ten en cuenta que
formatNights()
muestra un tipoSpanned
, que es una string con formato HTML. - 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. - Abre SleepTrackerViewModel. En la clase
SleepTrackerViewModel
, debajo de la definición deuiScope
, define una variable llamadanights
. Obtén todas las noches de la base de datos y asígnalas a la variablenights
.
private val nights = database.getAllNights()
- Debajo de la definición de
nights
, agrega código para transformarnights
ennightsString
. Usa la funciónformatNights()
deUtil.kt
.
Pasanights
a la funciónmap()
de la claseTransformations
. Para obtener acceso a los recursos de strings, define la función de mapeo como llamando aformatNights()
. Proporcionanights
y un objetoResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Abre el archivo de diseño
fragment_sleep_tracker.xml
. En la propiedadandroid:text
, enTextView
, puedes reemplazar la string del recurso por una referencia anightsString
.
"@{sleepTrackerViewModel.nightsString}"
- 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.
- 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.
.
- Agrega
onStopTracking()
aViewModel
. Inicia una corrutina en eluiScope
. Si aún no se establece la hora de finalización, estableceendTimeMilli
en la hora actual del sistema y llama aupdate()
con los datos nocturnos.
En Kotlin, la sintaxisreturn@
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)
}
}
- Implementa
update()
con el mismo patrón que usaste para implementarinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- 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 astop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Compila y ejecuta tu app.
- 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
- Del mismo modo, implementa
onClear()
yclear()
.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- Para conectar el controlador de clics a la IU, abre
fragment_sleep_tracker.xml
y agrega el controlador de clics aclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Compila y ejecuta tu app.
- 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 yDispartcher.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:
- Inicia una corrutina que se ejecute en el subproceso principal o de la IU, ya que el resultado afecta a la IU.
- 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.
- 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.
- 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:
- Patrón de fábrica
- Codelab de corrutinas
- Corrutinas, documentación oficial
- Contexto de corrutinas y despachadores
Dispatchers
- Supera el límite de velocidad de Android
Job
launch
- Muestra y salta en Kotlin
- CDATA significa datos de caracteres. CDATA significa que los datos entre estas strings incluyen datos que podrían interpretarse como lenguaje de marcado XML, pero no deberían hacerlo.
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:
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.