Crea un widget con Glance

1. Antes de comenzar

En este codelab, aprenderás el proceso de crear un widget de app para SociaLite. Primero, crearás un widget simple de Glance y lo agregarás a SociaLite y a la pantalla principal. Luego, agregarás al widget un estado cero usando componentes y un tema de Glance. A continuación, en el codelab se explicará cómo brindar compatibilidad con una interacción del usuario para seleccionar el contacto favorito desde el widget. Por último, aprenderás a actualizar el widget desde tu app.

Requisitos previos

  • Conocimientos básicos de Kotlin
  • Haber completado el codelab sobre configuración de Android Studio o saber cómo usar Android Studio y probar apps en un emulador de Android 15 o un dispositivo físico que ejecute Android 15
  • Conocimientos básicos de Hilt
  • Conocimientos básicos de Compose (Glance no usa funciones de componibilidad de Jetpack Compose, pero sí reutiliza el framework y el estilo de programación)

Qué aprenderás en este codelab

  • Cómo configurar tu app para que sea compatible con los widgets
  • Cómo usar componentes de Glance para crear un diseño responsivo
  • Cómo usar GlanceTheme para admitir colores dinámicos en las pantallas principales de los usuarios
  • Cómo controlar las interacciones del usuario en tu widget
  • Cómo actualizar tu widget desde la app

Requisitos

  • La versión más reciente de Android Studio
  • Un dispositivo de prueba o un emulador que ejecuten Android 12 o una versión posterior
  • El SDK de Android 12 o una versión posterior

Una pantalla principal de Android con el widget de SociaLite

2. Prepárate

Obtén el código de partida

  1. Si ya completaste los codelabs "Cómo cumplir con los requisitos de borde a borde de Android 15" o "Cómo agregar animaciones del gesto atrás predictivo", avanza a la sección Agrega un widget porque ya tienes el código de partida.
  2. Descarga el código de partida de GitHub.

También puedes clonar el repositorio y consultar la rama codelab_improve_android_experience_2024.

 git clone git@github.com:android/socialite.git
 cd socialite
 git checkout codelab_improve_android_experience_2024
  1. Abre SociaLite en Android Studio y ejecuta la app en tu dispositivo o emulador con Android 15. Verás una pantalla como la siguiente:

fb043d54dd01b3e5.png

SociaLite con navegación por gestos

3. Agrega un widget

¿Qué son los widgets?

Un widget es una parte de tu app que se puede incorporar en otras apps para Android. Con mayor frecuencia, se trata de la pantalla principal del usuario.

Al agregar widgets a tu app, los usuarios pueden iniciar tareas comunes rápidamente, ver información de un vistazo y personalizar su dispositivo con tu contenido.

¿Qué es Glance?

Jetpack Glance es una biblioteca para escribir widgets usando una API similar a la de Compose en Kotlin. Tiene varias ventajas de Compose, como la recomposición, el código declarativo de la IU escrito en Kotlin y los componentes definidos. Glance elimina gran parte de la necesidad de usar vistas remotas XML en tus widgets.

Crea un widget

Los widgets en Android se declaran en el AndroidManifest como un elemento <receiver>. Este receptor debe exportarse, controlar el intent de acción android.appwidget.action.APPWIDGET_UPDATE y proporcionar un archivo de configuración del widget de la app a través de un elemento de metadatos llamado android.appwidget.provider.

Agrega un widget a SociaLite

Una pantalla principal de Android con el widget de SociaLite

Te recomendamos que agregues un widget a SociaLite que les permita a los usuarios ver su contacto favorito y si tienen mensajes no leídos de esa persona. Si es así, al presionar el widget, el usuario debería poder chatear con su contacto favorito. Además, puedes utilizar componentes y temas de Glance para asegurarte de que tu widget se vea de manera óptima con un diseño responsivo y color dinámico.

Para comenzar, agregarás un widget estático "Hello World" a SociaLite. Luego, ampliarás las capacidades del widget.

Para ello, haz lo siguiente:

  1. Agrega las dependencias de Glance a tu app.
  2. Crea una implementación de GlanceAppWidget.
  3. Crea un GlanceAppWidgetReceiver.
  4. Configura el widget con un archivo en formato XML de información sobre el widget de la app.
  5. Agrega el receptor y la información sobre el widget de la app al archivo AndroidManifest.xml.

Agrega Glance a tu proyecto

El código de partida agregó las versiones de Glance y las coordenadas de la biblioteca al catálogo de versiones de SociaLite: libs.versions.toml.

libs.versions.toml

[versions]
//..

glance = "1.1.1"

[libraries]
glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" }
glance-material = { group = "androidx.glance", name = "glance-material3", version.ref = "glance" }

Además, se incluyeron las dependencias de Glance en el archivo app/build.gradle.kts de SociaLite.

build.gradle.kts
dependencies {
...
implementation(libs.glance.appwidget)
implementation(libs.glance.material)

...
}
  • Si modificaste estos archivos, sincroniza el proyecto para descargar las bibliotecas de Glance.

Crea tu GlanceAppWidget y GlanceAppWidgetReceiver

Android usa un receptor de transmisiones para alertar a SociaLite que un widget se agregó, se debe actualizar o se quitó. Glance proporciona una clase de receptor abstracta, GlanceAppWidgetReceiver, que extiende AppWidgetProvider.

Las implementaciones de GlanceAppWidgetReceiver también son responsables de proporcionar instancias de GlanceAppWidget. Esta clase renderiza las funciones de componibilidad de Glance en vistas remotas.

El código de partida incluye dos clases: SocialiteAppWidget, que extiende GlanceAppWidget, y SocialiteAppWidgetReceiver, que extiende GlanceAppWidgetReceiver.

Para comenzar, sigue estos pasos:

  1. Navega al paquete widget en app/src/main/java/com/google/android/samples/socialite/.
  2. Abre la clase SociaLiteAppWidget. Esta clase anula el método provideGlance.
  3. Reemplaza TODO por una llamada a provideContent y pasa la función de componibilidad de tu widget como un parámetro. Por ahora, el widget solo muestra el mensaje Hello World, pero agregarás otras funciones más adelante en este codelab.
package com.google.android.samples.socialite.widget

import android.content.Context
import androidx.glance.GlanceId
import androidx.glance.GlanceTheme
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.text.Text

class SociaLiteAppWidget : GlanceAppWidget() {
   override suspend fun provideGlance(context: Context, id: GlanceId) {
       provideContent {
           GlanceTheme {
               Text("Hello World")
           }
       }
   }
}
  1. Abre la clase SociaLiteAppWidgetReceiver en el paquete widget. Por ahora, el receptor proporciona una instancia de tu SociaLiteWidget, pero agregarás otras funciones en una sección más adelante.
  2. Reemplaza TODO por el constructor SociaLiteAppWidget():
package com.google.android.samples.socialite.widget

import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver

class SociaLiteAppWidgetReceiver : GlanceAppWidgetReceiver() {
   override val glanceAppWidget: GlanceAppWidget = SociaLiteAppWidget()
}

Ya está todo listo para configurar Android para que muestre tu widget y les permita a los usuarios agregarlo a la pantalla principal.

Agrega información de proveedor de app-widget

  1. Haz clic con el botón derecho en **res/xml** > New > XML resource file.
  2. Ingresa socialite_widget_info como el nombre del archivo y appwidget-provider como el elemento raíz. Luego, haz clic en OK. Este archivo incluye los metadatos de tu appwidget que utiliza un AppWidgetHost para mostrar el widget al principio.
  3. Agrega el siguiente código al archivo socialite_widget_info.xml:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
   android:resizeMode="horizontal|vertical"
   android:updatePeriodMillis="3600000"
   android:minHeight="128dp"
   android:minWidth="128dp"

   android:minResizeHeight="128dp"
   android:minResizeWidth="128dp"
   android:configure="com.google.android.samples.socialite.widget.SociaLiteAppWidgetConfigActivity"
   android:widgetFeatures="configuration_optional|reconfigurable"
   android:previewImage="@drawable/widget_preview"
   android:maxResizeHeight="512dp"
   android:maxResizeWidth="512dp"
   android:targetCellWidth="2"
   android:targetCellHeight="2"
   android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

En la siguiente tabla, se proporciona una descripción general de los atributos en este código y se detalla cada uno de ellos:

Nombre del atributo

Descripción

resizeMode

El widget puede cambiar de tamaño vertical y horizontalmente.

targetCellWidth, targetCellHeight

Especifica el tamaño predeterminado del widget cuando se agregue a la pantalla principal.

updatePeriodMillis

Controla cuándo el host del widget podría decidir actualizarlo. Tu app puede actualizar el widget cuando se esté ejecutando y tenga información nueva para mostrar.

minResizeHeight,minResizeWidth

Establece el tamaño mínimo del widget.

minHeight,minWidth

Especifica el tamaño mínimo predeterminado del widget cuando se agrega a la pantalla principal.

initialLayout

Proporciona un diseño inicial que se muestra mientras Glance renderiza las funciones de componibilidad.

previewImage

Proporciona una imagen estática del widget que se mostrará en el selector.

widgetFeatures

Indica las diferentes funciones que admite el widget. Son sugerencias para el host y no cambian el comportamiento del widget.

En este codelab, tus parámetros le indican al host que el widget no requiere configuración antes de agregarse a la pantalla principal y que se puede configurar después.

configure

El nombre de la clase de actividad de configuración. Se trata de una actividad que configura el widget más adelante.

Para ver todos los atributos disponibles, incluidas las funciones de la API 31 o posterior, consulta AppWidgetProviderInfo.

Actualiza AndroidManifest y realiza pruebas

Ya tienes todo listo para actualizar el archivo AndroidManifest.xml y probar tu widget. Define un receiver como elemento secundario del elemento application en el archivo. Este receptor controla el intent APPWIDGET_UPDATE y le brinda metadatos de tu appwidget al Launcher de Android.

Para comenzar, sigue estos pasos:

  1. Crea un elemento receiver para SociaLiteAppWidgetReceiver para que se exporte. Copia y pega lo siguiente en el archivo AndroidManifest.xml después del elemento application:
<receiver
   android:name=".widget.SociaLiteAppWidgetReceiver"
   android:exported="true"
   android:label="Favorite Contact">

   <intent-filter>
       <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
   </intent-filter>

   <meta-data
       android:name="android.appwidget.provider"
       android:resource="@xml/socialite_widget_info" />
</receiver>
  1. Compila y ejecuta tu app.
  2. Con la app en ejecución, agrega el widget a tu pantalla principal. Por ejemplo, en un dispositivo Pixel, mantén presionado el fondo y selecciona Widgets > SociaLite. Deberías poder agregar tu widget a la pantalla principal.

Una pantalla principal de Android con el widget en desarrollo. El widget es transparente y muestra el mensaje

El widget dice "Hello World" y tiene un fondo transparente. Claro que aún no tiene el mejor aspecto ni es el más funcional. En la siguiente sección, agregarás un diseño más complicado y embellecerás el widget con un toque de color de Material Design.

4. Mejora el diseño

Ahora tienes un widget estático al que le faltan muchas de las funciones que conforman un widget útil. Un widget útil realiza lo siguiente:

  • Mantiene el contenido breve y actual, y la funcionalidad simple.
  • Minimiza los espacios raros con un diseño que se puede cambiar de tamaño.
  • Aplica color a partir del fondo del host del widget de la app.

Consulta un análisis más detallado de qué factores conforman un buen widget en Widgets.

Agrega andamiaje

Ahora actualizarás el widget para que muestre el componente Scaffold de Glance.

La biblioteca de Glance proporciona Scaffold. Es una API de ranuras simple para mostrar la IU de un widget con una TitleBar. Establece el color de fondo en GlanceTheme.colors.widgetBackground y aplica padding. Será tu componente de nivel superior.

Para comenzar, sigue estos pasos:

  1. Reemplaza tu implementación de SociaLiteAppWidget por el siguiente código:
package com.google.android.samples.socialite.widget

import android.content.Context
import androidx.compose.runtime.Composable
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.ImageProvider
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.components.Scaffold
import androidx.glance.appwidget.components.TitleBar
import androidx.glance.appwidget.provideContent
import androidx.glance.layout.fillMaxSize
import androidx.glance.text.Text
import com.google.android.samples.socialite.R

class SociaLiteAppWidget : GlanceAppWidget() {
   override suspend fun provideGlance(context: Context, id: GlanceId) {
       provideContent {
           GlanceTheme() {
               Content()
           }
       }
   }

   @Composable
   private fun Content() {
       Scaffold(titleBar = {TitleBar(startIcon = ImageProvider(R.drawable.ic_launcher_monochrome), title = "SociaLite")},
           modifier = GlanceModifier.fillMaxSize()) {
           Text("Hello World")
       }
   }
}
  1. Para ver tus actualizaciones, vuelve a ejecutar la app y agrega una nueva copia del widget a la pantalla principal.

Una pantalla principal de Android con el widget en desarrollo. El widget tiene un fondo opaco y el título

Recuerda que los widgets son vistas remotas que muestra un host externo. Más adelante, agregarás la habilidad de actualizar automáticamente el widget dentro de la app. Hasta entonces, deberás agregar el widget desde el selector para ver los cambios del código.

Como ves, ha mejorado mucho. Sin embargo, cuando lo comparas con otros widgets, los colores no parecen adecuados. En las pantallas principales, se espera que los widgets establezcan sus colores en función del tema elegido por el usuario. Los tokens de color dinámico permiten que el tema de tu widget se adapte al fondo de pantalla y al tema de tu dispositivo.

Agrega color dinámico

Ahora agregarás el token de color widgetBackground al fondo del andamiaje y los tokens de color onSurface al texto de TitleBar y los componentes Text. Para actualizar el estilo del texto, debes importar la clase TextStyle de Glance. Para actualizar el fondo del andamiaje, configura la propiedad backgroundColor de Scaffold en GlanceTheme.colors.widgetBackground.

Para comenzar, sigue estos pasos:

  1. Incluye la nueva importación en el archivo SociaLiteAppWidget.kt.
//Add to the imports section of your Kotlin code.
import androidx.glance.text.TextStyle
  1. Actualiza tu función de componibilidad Content para agregar widgetBackground.
Scaffold(
   titleBar = {
       TitleBar(
           textColor = GlanceTheme.colors.onSurface,
           startIcon = ImageProvider(R.drawable.ic_launcher_monochrome),
           title = "SociaLite",
       )
   },
   backgroundColor = GlanceTheme.colors.widgetBackground,
   modifier = GlanceModifier.fillMaxSize(),
) {
   Text(text = "Hello World", style = TextStyle(color = GlanceTheme.colors.onSurface))
}
  1. Para ver tus actualizaciones, vuelve a ejecutar la app y agrega una nueva copia del widget a la pantalla principal.

Coincide con los temas de otros widgets en la pantalla principal y actualiza automáticamente los colores si cambias el fondo o estableces el modo oscuro. Si quieres un fondo muy colorido, el widget se adaptará a la sección del fondo donde se ubica.

Una pantalla principal de Android con el widget de Hello World con un tema claroPantalla principal con un tema claro

Una pantalla principal de Android con el widget de Hello World con un tema oscuroPantalla principal con un tema oscuro

Agrega un estado cero

Ahora debes dedicarte al estado y la configuración de tu widget. Cuando se agrega un widget a la pantalla principal y requiere configuración, lo mejor suele ser mostrar un estado cero. El estado cero le indica al usuario que configure el widget. Agregas una actividad de configuración a tu widget y creas un vínculo a ella desde el estado cero.

Este codelab proporciona clases para almacenar el estado de configuración de un widget, acceder a él y modificarlo. Agregarás código para actualizar la IU de tu widget de modo que muestre este estado, y crearás una acción de lambda para controlar las acciones de presión de un usuario.

Revisa el modelo del widget

Dedica un momento a revisar las clases del paquete com.google.android.samples.socialite.widget.model.

Esto incluye la clase WidgetModel y las clases WidgetModelDao y WidgetModelRepository. Estas clases ya están presentes en el código de partida del codelab y se encargan de conservar el estado de los widgets en la base de datos subyacente de Room. Además, estas clases emplean Hilt para administrar sus ciclos de vida.

La clase WidgetModel contiene un widgetId que asigna Android, el contactId del contacto de SociaLite que muestra, un displayName y una photo que se mostrarán, y un booleano si el contacto tiene mensajes no leídos. Las funciones de componibilidad de SociaLiteAppWidget los consumen y se muestran en el widget.

WidgetModelDao es un objeto de acceso a datos que abstrae el acceso a la base de datos de SociaLite. WidgetModelRepository proporciona funciones útiles para crear, leer, actualizar y borrar instancias de WidgetModel. Hilt crea estas clases, que se insertan en la app con la inserción de dependencias.

  • Abre el archivo WidgetModel.kt en el paquete model que se encuentra en app/src/main/java/com/google/android/samples/socialite/widget/model/.

Es una clase data con una anotación Entity. Android asigna un ID dedicado a cada instancia del widget, y SociaLite utiliza este ID como clave primaria para los datos del modelo. Cada instancia del modelo registra la información básica del contacto asociado y si hay mensajes no leídos de ese contacto.

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = Contact::class,
            parentColumns = ["id"],
            childColumns = ["contactId"],
            onDelete = ForeignKey.CASCADE,
        ),
    ],
    indices = [
        Index("widgetId"),
        Index("contactId"),
    ],
)
data class WidgetModel(
    @PrimaryKey val widgetId: Int,
    val contactId: Long,
    val displayName: String,
    val photo: String,
    val unreadMessages: Boolean = false,
) : WidgetState

El estado cero

Te recomendamos que, si no hay ningún modelo disponible, tu función de componibilidad Content cargue el modelo del widget desde el WidgetModelRepository y muestre el estado cero; de lo contrario, te recomendamos mostrar el contenido normal de tu widget. Por ahora, este será tu mensaje "Hello World", pero en la próxima sección, crearás una interfaz mejor.

Reemplazarás tu función de componibilidad Content por una expresión when que muestre la función de componibilidad ZeroState o un marcador de posición Text.

  1. En el método provideGlance, fuera de la función de componibilidad, obtén una referencia al WidgetModelRepository y el ID de widget actual. Agrega las siguientes líneas antes de provideContent en el método SociaLiteAppWidget provideGlance.
override suspend fun provideGlance(context: Context, id: GlanceId) {
   val widgetId = GlanceAppWidgetManager(context).getAppWidgetId(id)
   val repository = WidgetModelRepository.get(context)

Es posible que también debas agregar estas importaciones:

import com.google.android.samples.socialite.widget.model.WidgetModel
import com.google.android.samples.socialite.widget.model.WidgetModelRepository
import com.google.android.samples.socialite.widget.model.WidgetState.Loading
import androidx.glance.appwidget.GlanceAppWidgetManager
  1. En la función de componibilidad Content, agrega el ID de widget y repositorio como parámetros, y utilízalos para cargar tu modelo. Actualiza la firma de la función de componibilidad Content y agrega la siguiente línea:
private fun Content(repository: WidgetModelRepository, widgetId: Int) {
   val model = repository.loadModel(widgetId).collectAsState(Loading).value
  1. Si Android Studio no agrega la siguiente importación automáticamente, agrégala de forma manual:
import androidx.compose.runtime.collectAsState

También deberás actualizar provideGlance para pasar el ID de widget y repositorio a Content.

Reemplaza provideGlance por lo siguiente:

override suspend fun provideGlance(context: Context, id: GlanceId) {
        val widgetId = GlanceAppWidgetManager(context).getAppWidgetId(id)
        val repository = WidgetModelRepository.get(context)

        provideContent {
            GlanceTheme {
                Content(repository, widgetId)
            }
        }
    }
  1. En tu función de componibilidad Content, determina qué estado mostrar en función de si existe un modelo. Mueve el Scaffold y el contenido del widget a la función de componibilidad ZeroState reemplazando el componente Scaffold y su contenido por lo siguiente en un bloque:
   when (model) {
       is WidgetModel -> {Text("Hello World")}
       else -> ZeroState(widgetId)
   }

La función de componibilidad ZeroState ya está incluida en el código de partida en el paquete com.google.android.samples.socialite.widget.ui.

  1. Si Android Studio no importó automáticamente el paquete com.google.android.samples.socialite.widget.ui, agrega el siguiente código a la sección de importaciones de SociaLiteAppWidget.
import com.google.android.samples.socialite.widget.ui.ZeroState
  1. Para ver tus actualizaciones, vuelve a ejecutar la app y agrega una nueva copia del widget a la pantalla principal. Verás que el widget muestra el componente ZeroState y un botón. El botón abrirá la actividad de configuración cuando hagas clic en él, y, en la siguiente sección, actualizarás el estado de tu widget a partir de esta actividad.

Una pantalla principal de Android con el estado cero del widget de SociaLite. El estado cero tiene solo un botón.

La actividad de configuración de AppWidget de SociaLite muestra 4 contactos para elegir.

La actividad de configuración

Revisa la función de componibilidad ZeroState. Esta función se encuentra en el paquete com.google.android.samples.socialite.widget.ui en el archivo ZeroState.kt.

@Composable
fun ZeroState(widgetId: Int) {
   val widgetIdKey = ActionParameters.Key<Int>(AppWidgetManager.EXTRA_APPWIDGET_ID)
Scaffold(
   titleBar = {
       TitleBar(
           modifier = GlanceModifier.clickable(actionStartActivity(MainActivity::class.java)),
           textColor = GlanceTheme.colors.onSurface,
           startIcon = ImageProvider(R.drawable.ic_launcher_monochrome),
           title = "SociaLite",
       )
   },
   backgroundColor = GlanceTheme.colors.widgetBackground,
   modifier = GlanceModifier.fillMaxSize(),
) {
   Box(modifier = GlanceModifier.fillMaxSize(), contentAlignment = Alignment.Center) {
       Button(
           text = "Select Favorite Contact",
           onClick = actionStartActivity<SociaLiteAppWidgetConfigActivity>(
               parameters = actionParametersOf(widgetIdKey to widgetId),
           ),
       )
   }
}

}

La función de componibilidad Scaffold se mueve a la función de componibilidad ZeroState. TitleBar tiene el modificador clickable, que abre la actividad principal de SociaLite. Tu ZeroState usa la función de componibilidad Button de Glance para mostrar un llamado a la acción al usuario y, cuando hace clic en él, abre la actividad SociaLiteAppWidgetConfigActivity e incluye el ID de widget como extra de intent. Ambas acciones usan la función útil actionStartActivity de Glance. Para obtener más información sobre las acciones, consulta Controla la interacción del usuario.

  1. Revisa cómo se usa SociaLiteAppWidgetConfigActivity para actualizar la configuración de tu widget. Esta clase también es la actividad de configuración para tu widget. Las actividades de configuración leen el extra de número entero de intent con la clave AppWidgetManager.*EXTRA_APPWIDGET_ID.* Para obtener más información sobre las actividades de configuración, consulta Permite que los usuarios configuren widgets de apps.
  2. En SociaLiteAppWidgetConfigActivity, reemplaza TODO en la propiedad ContactRow onClick por el siguiente código:
{
  coroutineScope.launch {

     widgetModelRepository.createOrUpdate(
       WidgetModel(
           appWidgetId,
           contact.id,
           contact.name,
           contact.iconUri.toString(),
           false,
       ),
     )
     SociaLiteAppWidget().updateAll(this@SociaLiteAppWidgetConfigActivity)
     val resultValue = Intent().putExtra(
       AppWidgetManager.EXTRA_APPWIDGET_ID,
       appWidgetId,
     )
     setResult(RESULT_OK, resultValue)
     finish()
  }
}

Agrega las siguientes inclusiones si Android Studio no lo hace automáticamente

import com.google.android.samples.socialite.widget.model.WidgetModel
import androidx.glance.appwidget.updateAll
import kotlinx.coroutines.launch

Este bloque de código actualiza el estado de tu widget. Primero, usa el repositorio para guardar o actualizar el WidgetModel con la información del contacto seleccionado. Luego, llama a la función de suspensión updateAll. Esta función actualiza todos los widgets en la pantalla principal, y se la puede llamar desde cualquier parte de tu app. Por último, el bloque establece el resultado de la actividad de configuración para indicar que actualizó el widget correctamente.

  1. Ejecuta y reemplaza tu widget en la pantalla principal. Deberías ver el nuevo estado cero.

Una pantalla principal de Android con el estado cero del widget de SociaLite. El estado cero tiene solo un botón.

  1. Haz clic en Seleccionar contacto favorito. Esto te lleva a la actividad de configuración.

Captura de pantalla de la actividad de configuración que muestra los cuatro contactos para elegir Una pantalla principal de Android con el widget de Hello World con un tema claro

  1. Selecciona un contacto. Esto actualiza tu widget. Sin embargo, el widget aún no muestra tu contacto favorito porque agregarás esa capacidad en la siguiente sección.

Administra los datos del widget

  1. Abre la herramienta App inspection, conéctate a un proceso si es necesario y selecciona la pestaña Inspector de bases de datos para observar el contenido de la base de datos de la app.
  2. Selecciona un contacto favorito en el widget y comprueba si se actualiza a "Hello World". De regreso en la herramienta de inspección de apps, deberías ver una pestaña Widget model con una entrada para tu widget. Tal vez debas actualizar la tabla o presionar Live updates para ver los cambios.

dd030cce6a75be25.png

  1. Agrega otro widget y selecciona otro contacto. Tal vez debas presionar Refresh table o Live updates para ver el modelo nuevo.
  2. Quita el widget y comprueba que el modelo queda en la base de datos después de quitarlo.

Para actualizar SociaLiteAppWidgetReceiver y limpiar tu base de datos cuando se quita un widget, anula onDeleted.

Para limpiar modelos de widget huérfanos, puedes llamar a WidgetModelRepository.cleanupWidgetModels. Hilt administra la clase de repositorio, y debes usar la inserción de dependencias para acceder a su instancia.

  1. En SociaLiteAppWidgetReceiver, agrega la anotación AndroidEntryPoint de Hilt a tu declaración de clase de receptor e inserta la instancia de WidgetModelRepository.
  2. Llama a WidgetModelRepository.cleanupWidgetModels en la anulación de método para onDeleted.

Tu código debería verse de la siguiente manera:

package com.google.android.samples.socialite.widget

import android.content.Context
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import com.google.android.samples.socialite.widget.model.WidgetModelRepository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class SociaLiteAppWidgetReceiver : GlanceAppWidgetReceiver() {
   override val glanceAppWidget: GlanceAppWidget = SociaLiteAppWidget()

   @Inject
   lateinit var repository: WidgetModelRepository

   override fun onDeleted(context: Context, appWidgetIds: IntArray) {
       super.onDeleted(context, appWidgetIds)
       repository.cleanupWidgetModels(context)
   }

}
  1. Vuelve a ejecutar la app. Deberías ver que la fila del modelo se quita del inspector de apps cuando se quita el widget de la pantalla principal.

5. Agrega la IU de contactos y actualízala cuando lleguen nuevos mensajes

Estás en la recta final del codelab. En esta sección, implementarás la IU de contactos final para el widget y la actualizarás cuando se reciba un mensaje no leído del contacto.

  1. Revisa la clase WidgetModelRepository en el paquete model.

Aquí es donde tienes el método útil updateUnreadMessagesForContact; actualiza los widgets asociados con el ID de un contacto.

//Don't add this code.
fun updateUnreadMessagesForContact(contactId: Long, unread: Boolean) {
   coroutineScope.launch {
       widgetModelDao.modelsForContact(contactId).filterNotNull().forEach { model ->
           widgetModelDao.update(
             WidgetModel(model.widgetId, model.contactId, model.displayName, model.photo, unread)
           )
           SociaLiteAppWidget().updateAll(appContext)
       }
   }
}

Este método tiene dos parámetros: contactId, el ID del contacto que se actualizará, y unread, un booleano del estado del mensaje no leído. Este método usa WidgetModelDao para encontrar todos los modelos de widget que muestran este contacto y actualizan el modelo con el nuevo estado de leído. Luego, la función llama al método SociaLiteAppWidget().updateAll que proporcionó Glance para actualizar todos los widgets en la pantalla principal del usuario.

Ahora que sabes cómo se actualizan los widgets y su estado, puedes crear tu IU de contacto, enviar un mensaje y ver cómo se actualiza. Para ello, actualiza SociaLiteAppWidget con una función de componibilidad FavoriteContact en el diseño de tu widget. En este diseño, también comprueba si deberías mostrar No new messages o New Messages!.

  1. Revisa el archivo FavoriteContact.kt en el paquete com.google.android.samples.socialite.widget.ui.
//Don't add this code.
@Composable
fun FavoriteContact(model: WidgetModel, onClick: Action) {
   Column(
       modifier = GlanceModifier.fillMaxSize().clickable(onClick)
           .background(GlanceTheme.colors.widgetBackground).appWidgetBackground()
           .padding(bottom = 8.dp),
       verticalAlignment = Alignment.Vertical.Bottom,
       horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
   ) {
       Image(
           modifier = GlanceModifier.fillMaxWidth().wrapContentHeight().defaultWeight()
               .cornerRadius(16.dp),
           provider = ImageProvider(model.photo.toUri()),
           contentScale = ContentScale.Crop,
           contentDescription = model.displayName,
       )
       Column(
           modifier = GlanceModifier.fillMaxWidth().wrapContentHeight().padding(top = 4.dp),
           verticalAlignment = Alignment.Vertical.Bottom,
           horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
       ) {
           Text(
               text = model.displayName,
               style = TextStyle(
                   fontWeight = FontWeight.Bold,
                   fontSize = 24.sp,
                   color = (GlanceTheme.colors.onSurface),
               ),
           )

           Text(
               text = if (model.unreadMessages) "New Message!" else "No messages",
               style = TextStyle(
                   fontWeight = FontWeight.Bold,
                   fontSize = 16.sp,
                   color = (GlanceTheme.colors.onSurface),
               ),
           )
       }
   }
}
  1. Reemplaza Text("Hello World") en la función de componibilidad Content de SociaLiteAppWidget por una llamada tu función de componibilidad FavoriteContact.

Esta función de componibilidad tomará el WidgetModel y una Action creada por la función actionStartActivity de Glance.

  1. Agrega la llamada a tu bloque when antes de ZeroState cuando el modelo no sea WidgetModel.
when (model) {
  is WidgetModel -> FavoriteContact(model = model, onClick = actionStartActivity(
     Intent(LocalContext.current.applicationContext, MainActivity::class.java)
         .setAction(Intent.ACTION_VIEW)
         .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
         .setData("https://socialite.google.com/chat/${model.contactId}".toUri()))
  )
  else -> ZeroState(widgetId)
}
  1. Si Android Studio no agrega automáticamente las siguientes importaciones, hazlo ahora:
import com.google.android.samples.socialite.widget.ui.FavoriteContact
import androidx.glance.appwidget.action.actionStartActivity
import android.content.Intent
import com.google.android.samples.socialite.MainActivity
import androidx.core.net.toUri
  1. Ejecuta tu app.
  2. Selecciona un contacto favorito, envía un mensaje y cierra la app de inmediato antes de que el contacto responda. El estado del widget debería cambiar cuando llegue la respuesta.
  3. Haz clic en el widget para abrir el chat y comprueba que el estado se haya vuelto a actualizar cuando regreses a la pantalla principal.

Una pantalla principal de Android con el widget de SociaLite finalizado con un tema claro.

6. Felicitaciones

Completaste correctamente el codelab y aprendiste a escribir un widget con Glance. Ya tienes todas las herramientas para crear un widget atractivo que se vea bien en muchas pantallas principales, controle las entradas del usuario y se actualice solo.

Para obtener el código de la solución en la rama main, sigue estos pasos:

  1. Si ya descargaste SociaLite, ejecuta este comando:
git checkout main
  1. De lo contrario, vuelve a descargar el código para ver la rama main:
git clone git@github.com:android/socialite.git

Más información