使用 Glance 创建 widget

1. 准备工作

此 Codelab 将引导您完成为 SociaLite 创建应用 widget 的整个过程。首先,您将构建一个简单的 Glance widget,并将其添加到 SociaLite 和主屏幕中。接下来,您将使用 Glance 组件和 Glance 主题为 widget 添加零状态。然后,此 Codelab 将引导您支持用户互动,以便从 widget 中选择收藏的联系人。最后,您将了解如何从应用中更新 widget。

前提条件

  • 具备 Kotlin 基础知识。
  • 已完成设置 Android Studio Codelab,或者熟悉如何在 Android 15 模拟器或搭载 Android 15 的实体设备中使用 Android Studio 并测试应用。
  • 具备基本的 Hilt 知识。
  • 具备 Compose 基础知识。Glance 不使用 Jetpack Compose 中的可组合项,但会重复使用框架和编码样式。

您将在此 Codelab 中学到的内容

  • 如何将应用配置为支持 widget。
  • 如何使用 Glance 组件构建自适应布局。
  • 如何使用 GlanceTheme 在用户的主屏幕上支持动态颜色。
  • 如何在 widget 中处理用户互动。
  • 如何在应用中更新 widget。

所需条件

  • 最新版本的 Android Studio。
  • 搭载 Android 12 或更高版本的测试设备或模拟器。
  • Android 12 或更高版本的 SDK。

一个显示 Socialite widget 的 Android 主屏幕

2. 进行设置

获取起始代码

  1. 如果您已完成“应对 Android 15 强制执行的无边框措施”或“添加预测性返回动画”Codelab,请前往添加 widget 部分,因为您已有起始代码。
  2. 从 GitHub 下载起始代码

或者,您也可以克隆代码库并查看 codelab_improve_android_experience_2024 分支。

 git clone git@github.com:android/socialite.git
 cd socialite
 git checkout codelab_improve_android_experience_2024
  1. 在 Android Studio 中打开 SociaLite,然后在搭载 Android 15 的设备或模拟器上运行该应用。您将看到如下所示的界面:

fb043d54dd01b3e5.png

采用手势导航的 SociaLite

3. 添加微件

widget 是什么?

widget 是可以嵌入到其他 Android 应用中的应用组件。最常见的是用户的主屏幕。

向应用添加 widget 可让用户快速启动常见任务、查看一目了然的信息,以及使用您的内容自定义设备。

Glance 是什么?

Jetpack Glance 是一个库,用于使用 Kotlin 中类似于 Compose 的 API 编写 widget。它具有与 Compose 相同的多项优势,例如重组、使用 Kotlin 编写的声明性界面代码以及规范化组件。Glance 消除了在 widget 中使用 XML 远程视图的大部分需求。

创建 widget

Android 上的 widget 在 AndroidManifest 中声明为 <receiver> 元素。此接收器应被导出,处理 android.appwidget.action.APPWIDGET_UPDATE 操作 intent,并通过名为 android.appwidget.provider 的元数据元素提供应用 widget 设置文件。

向 SociaLite 添加 widget

一个显示 Socialite widget 的 Android 主屏幕

您想向 Socialite 添加一个 widget,让用户可以查看他们收藏的联系人,并查看是否有来自该联系人的未读消息。如果有,点按该 widget 应将用户引导至他们收藏的联系人的聊天界面。此外,您还可以使用 Glance 组件和主题,通过采用自适应设计和动态颜色来确保 widget 外观最佳。

首先,您需要向 Socialite 添加静态“Hello World”widget。然后,您可以扩展该 widget 的功能。

为此,您需要执行以下操作:

  1. 将 Glance 依赖项添加到您的应用。
  2. 创建 GlanceAppWidget 的实现。
  3. 创建 GlanceAppWidgetReceiver
  4. 使用应用 widget 信息 XML 文件来配置该 widget。
  5. 将接收器和应用 widget 信息添加到 AndroidManifest.xml 文件中。

将 Glance 添加到您的项目中

起始代码已将 Glance 版本和库坐标添加到 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" }

此外,Glance 的依赖项包含在 SociaLite 的 app/build.gradle.kts 文件中。

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

...
}
  • 如果您修改了这些文件,请同步项目以下载 Glance 库。

创建 GlanceAppWidgetGlanceAppWidgetReceiver

Android 使用广播接收器来提醒 SociaLite,让其知道某个 widget 已添加、需要更新或已移除。Glance 提供了一个抽象接收器类 GlanceAppWidgetReceiver,它扩展了 AppWidgetProvider。

GlanceAppWidgetReceiver 实现还负责提供 GlanceAppWidget 的实例。此类会将 Glance 的可组合项渲染到远程视图中。

起始代码包含两个类:会扩展 GlanceAppWidgetSocialiteAppWidget,以及会扩展 GlanceAppWidgetReceiverSocialiteAppWidgetReceiver

若要开始,请按照以下步骤操作:

  1. 前往 app/src/main/java/com/google/android/samples/socialite/ 中的 widget 软件包。
  2. 打开 SociaLiteAppWidget 类。此类会替换 provideGlance 方法。
  3. TODO 替换为对 provideContent 的调用,然后将该 widget 的可组合函数作为参数传递。目前,该 widget 仅显示消息 Hello World,但在此 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. 打开 widget 软件包中的 SociaLiteAppWidgetReceiver 类。目前,您的接收器提供 SociaLiteWidget 的实例,但您将在后面的部分中添加更多功能。
  2. TODO 替换为 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()
}

现在,您可配置 Android 以显示该 widget,并允许用户将其添加到主屏幕。

添加应用 widget 提供程序信息

  1. 右键点击 **res/xml** > 新建 > XML 资源文件
  2. 输入 socialite_widget_info 作为文件名,并输入 appwidget-provider 作为根元素,然后点击确定。此文件包含 appwidget 的元数据,AppWidgetHost 将使用这些数据来初始显示该 widget。
  3. 将以下代码添加到 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>

下表简要介绍了此代码中的属性,并对每个属性进行了说明:

属性名称

说明

resizeMode

您的 widget 可能会在垂直和水平方向上调整大小。

targetCellWidth, targetCellHeight

指定将该 widget 添加到主屏幕时的默认大小。

updatePeriodMillis

控制 widget 主机何时可以决定刷新您的 widget。您的应用可以在运行时更新您的 widget,并显示新信息

minResizeHeight,minResizeWidth

设置可将该 widget 的尺寸调整到多小。

minHeight,minWidth

指定将该 widget 添加到主屏幕时的默认最小尺寸。

initialLayout

提供在 Glance 呈现可组合项时显示的初始布局。

previewImage

提供要在 widget 选择器中显示的 widget 静态图片。

widgetFeatures

指明该 widget 支持的各种功能。这些提示是针对 widget 主机的,实际上并不会更改该 widget 的行为。

在此 Codelab 中,您的标记会告知主机,在将该 widget 添加到主屏幕之前,该 widget 不需要进行配置,而是可以等到添加后再配置。

configure

配置 activity 类的名称。这是稍后会配置该 widget 的 activity。

如需查看所有可用属性(包括 API 31 或更高级别中的功能),请参阅 AppWidgetProviderInfo

更新 AndroidManifest 并进行测试

现在,您可以更新 AndroidManifest.xml 文件并测试您的 widget 了。您可以将 receiver 元素定义为文件中 application 元素的子元素。此接收器会处理 APPWIDGET_UPDATE intent,并向 Android 启动器提供 appwidget 元数据。

若要开始,请按照以下步骤操作:

  1. 为要导出的 SociaLiteAppWidgetReceiver 创建一个 receiver 元素。将以下代码复制并粘贴到 AndroidManifest.xml 文件中的 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. 编译并运行您的应用。
  2. 应用运行后,将该 widget 添加到主屏幕。例如,在 Pixel 上,长按背景,然后依次选择 widget > SociaLite。您应该能够将您的 widget 添加到主屏幕。

一个 Android 主屏幕,其中显示尚处于开发阶段的 widget。该 widget 是透明的,并显示消息

您的 widget 显示“Hello World”,并具有透明背景。诚然,这还不是外观最出众或功能最强大的 widget。在下一部分中,您将添加更复杂的布局,并使用 Material Design 颜色美化该 widget。

4. 改进设计

现在,您有了一个静态 widget,但它缺少优质 widget 需要具备的许多功能。优质 widget 要做到以下几点:

  • 保持内容新鲜且简短,并保持功能简单。
  • 能够通过可调整大小的布局最大限度地减少不合宜的间隙。
  • 采用应用 widget 主机背景中的颜色。

如需深入了解优质 widget 需要满足哪些条件,请参阅 widget

添加 Scaffold

现在,您可更新您的 widget,以便在 Glance Scaffold 组件中显示。

Scaffold 由 Glance 库提供。这是一个简单的槽 API,用于通过 TitleBar 显示 widget 界面。它会将背景颜色设为 GlanceTheme.colors.widgetBackground 并应用内边距。它将成为您的顶级组件。

若要开始,请按照以下步骤操作:

  1. SociaLiteAppWidget 的实现替换为以下代码:
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. 如需查看更新,请重新运行应用,然后将该 widget 的新副本添加到主屏幕。

一个 Android 主屏幕,其中显示尚处于开发阶段的 widget。该 widget 具有不透明背景和标题

请记住,widget 是由外部主机显示的远程视图。稍后,您将添加在应用内自动更新您的 widget 的功能。在那之前,您必须从 widget 选择器中添加该 widget 才能查看代码更改。

如您所见,这要好得多,但当您将自己的 widget 与其他 widget 进行比较时,颜色似乎不对。在主屏幕上,widget 应根据用户的主题设置来设置颜色。借助动态颜色令牌,您可以让 widget 的主题契合设备的壁纸和主题。

添加动态颜色

现在,您可以将 widgetBackground 颜色令牌添加到 Scaffold 背景,并将 onSurface 颜色令牌添加到 TitleBar 文本和文本组件。若要更新文本样式,您需要导入 TextStyle Glance 类。如需更新 Scaffold 背景,请将 ScaffoldbackgroundColor 属性设为 GlanceTheme.colors.widgetBackground

若要开始,请按照以下步骤操作:

  1. SociaLiteAppWidget.kt 文件中添加新的导入项。
//Add to the imports section of your Kotlin code.
import androidx.glance.text.TextStyle
  1. 更新 Content 可组合项以添加 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. 如需查看更新,请重新运行应用,然后将该 widget 的新副本添加到主屏幕。

它会与主屏幕上其他 widget 的主题相匹配,并在您更改背景或设置深色模式时自动更新颜色。对于色彩丰富的背景,该 widget 会根据其所在的背景部分进行调整。

一个以浅色主题显示“Hello World”widget 的 Android 主屏幕采用浅色主题的主屏幕

一个以深色主题显示“Hello World”widget 的 Android 主屏幕采用深色主题的主屏幕

添加零状态

现在,您需要考虑状态并配置您的 widget。当 widget 被添加到主屏幕且需要配置时,最好显示零状态。零状态旨在提示用户配置 widget。您可以向 widget 添加配置 activity,并从零状态链接到该 activity。

此 Codelab 提供了用于存储、访问和修改 widget 配置状态的类。您将添加代码来更新 widget 的界面以显示此状态,并创建 lambda 操作来处理用户点按操作。

查看 widget 模型

请花点时间查看软件包 com.google.android.samples.socialite.widget.model 中的类。

这包括 WidgetModel 类以及 WidgetModelDaoWidgetModelRepository 类。这些类已存在于 Codelab 起始代码中,并负责将 widget 的状态持久存储到底层 Room 数据库。此外,这些类使用 Hilt 来管理各自的生命周期。

WidgetModel 类包含由 Android 分配的 widgetId、所显示 SociaLite 联系人的 contactId、要显示的 displayNamephoto,以及表明相应联系人是否有未读消息的布尔值。这些数据将由 SociaLiteAppWidget 可组合项使用,并显示在 widget 中。

WidgetModelDao 是一个数据访问对象,用于抽象化对 SociaLite 数据库的访问。WidgetModelRepository 提供了用于创建、读取、更新和删除 WidgetModel 实例的便捷函数。这些类由 Hilt 创建,并通过依赖项注入功能注入到应用中。

  • 打开位于 app/src/main/java/com/google/android/samples/socialite/widget/model/model 软件包中的 WidgetModel.kt 文件。

它是一个带有 Entity 注解的 data 类。Android 会为每个 widget 实例分配一个专用 ID,而 SociaLite 会将此 ID 用作模型数据的主键。模型的每个实例都会跟踪所关联的联系人的基本信息,以及是否有来自此人的未读消息。

@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

零状态

您希望 Content 可组合项从 WidgetModelRepository 加载 widget 的模型并在没有可用模型时显示零状态;否则,您希望显示 widget 的常规内容。目前,这将是您的“Hello World”消息,但在下一部分中,您将创建更好的界面。

您将 Content 可组合项替换为显示 ZeroState 可组合项或 Text 占位符的 when 表达式。

  1. provideGlance 方法中,在可组合项之外,获取对 WidgetModelRepository 和当前 widget ID 的引用。在 SociaLiteAppWidget provideGlance 方法中的 provideContent 前面添加以下几行代码。
override suspend fun provideGlance(context: Context, id: GlanceId) {
   val widgetId = GlanceAppWidgetManager(context).getAppWidgetId(id)
   val repository = WidgetModelRepository.get(context)

您可能还需要添加以下导入项:

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. Content 可组合函数中,添加存储库和 widget ID 作为参数,并使用它们来加载模型。更新 Content 可组合项的函数签名,并添加以下代码行:
private fun Content(repository: WidgetModelRepository, widgetId: Int) {
   val model = repository.loadModel(widgetId).collectAsState(Loading).value
  1. 如果 Android Studio 未自动添加以下导入项,请手动添加:
import androidx.compose.runtime.collectAsState

您还需要更新 provideGlance,以将 widget ID 和存储库传递给 Content

provideGlance 替换为以下代码:

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. Content 可组合函数中,根据模型是否存在来确定要显示的状态。将 Scaffold 和 widget 内容移至 ZeroState 可组合项中,方法是将 Scaffold 组件及其内容替换为以下 when 代码块:
   when (model) {
       is WidgetModel -> {Text("Hello World")}
       else -> ZeroState(widgetId)
   }

ZeroState 可组合项已包含在 com.google.android.samples.socialite.widget.ui 软件包的起始代码中。

  1. 如果 Android Studio 未自动导入 com.google.android.samples.socialite.widget.ui 软件包,请将以下代码添加到 SociaLiteAppWidget 的“imports”部分。
import com.google.android.samples.socialite.widget.ui.ZeroState
  1. 如需查看更新,请重新运行应用,然后将该 widget 的新副本添加到主屏幕。您会看到该 widget 显示了 ZeroState 组件和一个按钮。当您点击该按钮时,它将打开配置 activity,在下一节中,您将通过此 activity 更新 widget 状态。

一个 Android 主屏幕,其中显示 Socialite widget 的零状态。零状态只有一个按钮。

SociaLite AppWidget 配置 activity,显示了 4 个可供选择的联系人。

配置 activity

查看 ZeroState 可组合函数。此函数位于 com.google.android.samples.socialite.widget.ui 软件包的 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),
           ),
       )
   }
}

}

Scaffold 可组合项已移至 ZeroState 可组合项中。TitleBar 具有 clickable 修饰符,用于打开 SociaLite 的主 activity。您的 ZeroState 使用 Glance Button 可组合项向用户显示号召性用语,点击该号召性用语会打开 SociaLiteAppWidgetConfigActivity activity,并将 widget ID 作为 intent extra 包含在内。这两项操作都使用 Glance 的 actionStartActivity 便捷函数。如需详细了解 Action,请参阅处理用户互动

  1. 了解如何使用 SociaLiteAppWidgetConfigActivity 更新 widget 的配置。此类也是您的 widget 的配置 activity。配置 activity 使用键 AppWidgetManager.*EXTRA_APPWIDGET_ID.* 读取 intent 整数 extra。如需详细了解配置 activity,请参阅允许用户配置应用 widget
  2. SociaLiteAppWidgetConfigActivity 中,将 ContactRow onClick 属性中的 TODO 替换为以下代码:
{
  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()
  }
}

如果 Android Studio 未自动添加以下包含项,请手动添加

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

此代码块会更新 widget 状态。首先,它会使用存储库保存 WidgetModel,或使用所选联系人的信息来更新它。接下来,它会调用 updateAll 挂起函数。此函数会更新主屏幕上的所有 widget,并且可从应用内任何位置调用。最后,此代码块会设定配置 activity 的结果,以表明它已成功更新 widget。

  1. 在主屏幕上运行并替换您的 widget。您应该会看到新的零状态。

一个 Android 主屏幕,其中显示 Socialite widget 的零状态。零状态只有一个按钮。

  1. 点击 Select favorite contact。这会将您转到配置 activity。

配置 activity 的屏幕截图,显示了 4 个可供选择的联系人 一个以浅色主题显示“Hello World”widget 的 Android 主屏幕

  1. 选择相应联系人。 这会更新您的 widget。不过,该 widget 尚未显示您收藏的联系人,因为您将在下一部分中添加相应功能。

管理 widget 数据

  1. 打开应用检查工具,根据需要连接到进程,然后选择数据库检查器标签页以查看应用数据库的内容。
  2. 在 widget 中选择一个收藏的联系人,看看是否会更新为“Hello World”。返回到“应用检查”工具,您应该会看到一个 widget 模型标签页,其中包含您的 widget 对应的条目。您可能需要刷新表格或按实时更新才能看到更改。

dd030cce6a75be25.png

  1. 再添加一个 widget,然后再选择一个联系人。您可能需要按刷新表格实时更新才能看到新模型。
  2. 移除该 widget 并注意,在移除 widget 后模型会保留在数据库中。

移除 widget 后,您可通过替换 onDeleted 来更新 SociaLiteAppWidgetReceiver,以便清理数据库。

如需清除孤立的 widget 模型,您可以调用 WidgetModelRepository.cleanupWidgetModels。存储库类由 Hilt 管理,您需要使用依赖项注入来访问其实例。

  1. SociaLiteAppWidgetReceiver 中,将 AndroidEntryPoint Hilt 注解添加到接收器类声明中,并注入 WidgetModelRepository 实例。
  2. onDeleted 的方法替换中调用 WidgetModelRepository.cleanupWidgetModels

您的代码应如下所示:

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. 重新运行应用。当从主屏幕中移除 widget 时,您应该会在应用检查器中看到模型行已被移除。

5. 添加联系人界面并在收到新消息时更新

您已进入 Codelab 的最后阶段。在这一部分中,您将为 widget 实现最终的联系人界面,并在有来自联系人的未读消息时更新该界面。

  1. 查看 model 软件包中的 WidgetModelRepository 类。

这是 updateUnreadMessagesForContact 便捷方法所在的位置;该方法可更新与联系人 ID 关联的 widget。

//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)
       }
   }
}

此方法有两个参数:contactId(要更新的联系人的 ID)和 unread(未读消息状态的布尔值)。此方法使用 WidgetModelDao 查找显示此联系人的所有 widget 模型,并使用新的已读状态更新模型。然后,该函数会调用 Glance 提供的 SociaLiteAppWidget().updateAll 方法来更新用户主屏幕上的所有 widget。

现在,您已经了解了如何更新 widget 及其状态,接下来您可以创建联系人界面、发送消息并观察其更新情况。为此,您需要在 widget 布局中使用 FavoriteContact 可组合项更新 SociaLiteAppWidget。在此布局中,您还需要检查应显示 No new messages 还是 New Messages!

  1. 查看 com.google.android.samples.socialite.widget.ui 软件包中的 FavoriteContact.kt 文件。
//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. SociaLiteAppWidgetContent 可组合项中的 Text("Hello World") 替换为对 FavoriteContact 可组合项的调用。

这个可组合项将接受 WidgetModel 以及由 actionStartActivity Glance 函数创建的操作。

  1. 如果模型不是 WidgetModel,请在 ZeroState 前添加对 when 代码块的调用。
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. 如果 Android Studio 未自动添加以下导入项,请立即手动添加:
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. 运行应用。
  2. 选择一个收藏的联系人,发送消息,然后在对方回复之前立即退出应用。收到响应后,widget 的状态应发生变化。
  3. 点击该 widget 以打开聊天界面,并在退出到主屏幕时查看状态是否再次更新。

一个 Android 主屏幕,其中以浅色主题显示已完成的 SociaLite widget。

6. 恭喜

您已成功完成此 Codelab,并学习了如何使用 Glance 编写 widget!您应该能够轻松创建一个精美的 widget,并且该 widget 在很多主屏幕上看起来都非常棒,能够处理用户输入并自行更新。

如需获取 main 分支中的解决方案代码,请按以下步骤操作:

  1. 如果您已下载 SociaLite,请运行以下命令:
git checkout main
  1. 如果还未下载,您可再次下载该代码,以查看 main 分支:
git clone git@github.com:android/socialite.git

了解详情