通过与应用有关的 Action 将动态快捷方式扩展到 Google 助理

1. 概览

上一个 Codelab 中,你使用静态快捷方式在示例应用中实现了常用的内置 intent (BII)。Android 开发者使用与应用有关的 Action 将应用功能扩展到 Google 助理。

静态快捷方式与应用捆绑,并且只能通过发布应用的新版本进行更新。为应用中的动态元素(例如用户生成的内容)启用语音功能可以通过动态快捷方式来实现。用户执行相关操作(例如在任务跟踪应用中创建新记事)后,应用会推送动态快捷方式。借助与应用有关的 Action,你可以将这些快捷方式与 BII 绑定,以启用语音功能,这样用户就可以通过说出“Hey Google, open my grocery list on ExampleApp”这样的语音指令从 Google 助理访问内容。

三个进程屏幕,显示的是 Google 助理正在启动一个动态快捷方式。

图 1. 三个进程屏幕,显示的是用户创建任务以及 Google 助理启动该任务项的动态快捷方式。

构建内容

在此 Codelab 中,你将在一个示例待办事项列表 Android 应用中启用动态语音快捷方式,让用户能够要求 Google 助理打开他们在应用中创建的任务列表项。为此,你可以使用 Android 架构模式,特别是代码库服务定位器ViewModel 模式。

前提条件

本 Codelab 是建立在上一个 Codelab 中涵盖的与应用有关的 Action 概念的基础上,尤其是 BII 和静态快捷方式。如果你不熟悉与应用有关的 Action,我们建议你先完成本 Codelab 的学习,然后再继续。

此外,请确保你的开发环境具有以下配置,然后再继续:

  • 已安装 git 的终端,用于运行 shell 命令。
  • Android Studio 的最新稳定版本。
  • 一部可以访问互联网的 Android 实体设备或虚拟设备。
  • 已登录 Android Studio、Google 应用和 Google 助理应用的 Google 帐号。

2. 了解运作方式

启用 Voice Access 的动态快捷方式涉及以下步骤:

  • 将动态快捷方式绑定到符合条件的 BII。
  • 通过添加 Google 快捷方式集成库,使 Google 助理能够提取快捷方式。
  • 在用户完成相关的应用内任务时推送快捷方式。

绑定快捷方式

要通过 Google 助理访问动态快捷方式,需要将其绑定到相关的 BII。当触发具有快捷方式的 BII 时,Google 助理会将用户请求中的参数与绑定的快捷方式中定义的关键字匹配。例如:

  • 绑定到 GET_THING BII 的快捷方式可让用户直接通过 Google 助理请求特定应用内内容。*“Hey Google, open my grocery list on ExampleApp.”
  • 绑定到 ORDER_MENU_ITEM BII 的快捷方式可让用户再次执行之前的订单。*“Hey Google, order my usual from ExampleApp.”

如需查看完整的 BII 分类列表,请参阅内置 intent 参考文档

为 Google 助理提供快捷方式

将快捷方式绑定到 BII 后,下一步是将 Google 快捷方式集成库添加到你的项目中,让 Google 助理能够提取这些快捷方式。添加此库后,Google 助理会识别你应用推送的每个快捷方式,让用户能够通过在 Google 助理中使用快捷方式的触发短语来启动这些快捷方式。

3. 准备开发环境

本 Codelab 使用专为 Android 构建的示例待办事项列表应用。借助此应用,用户可以将内容添加到列表中、按类别搜索任务列表项以及按完成状态过滤任务。完成此部分,下载并准备示例应用。

下载基础文件

运行以下命令以克隆示例应用的 GitHub 代码库:

git clone https://github.com/actions-on-google/app-actions-dynamic-shortcuts.git

克隆完代码库后,按照以下步骤在 Android Studio 中将其打开:

  1. Welcome to Android Studio 对话框中,点击 Import project
  2. 选择克隆代码库的文件夹。

或者,你也可以通过克隆其 GitHub 代码库的 codelab-complete 分支来查看代表已完成的 Codelab 的示例应用版本:

git clone https://github.com/actions-on-google/app-actions-dynamic-shortcuts.git --branch codelab-complete

添加 Shortcuts API 依赖项

将以下 Jetpack 库添加到 app/build.gradle 资源文件:

app/build.gradle

dependencies {
   ...
   // Shortcuts library
   implementation "androidx.core:core:1.6.0"
   implementation 'androidx.core:core-google-shortcuts:1.0.1'
   ...
}

添加唯一的应用 ID

在本 Codelab 后面的内容中,你将使用 Google 助理插件,让快捷方式在实体设备或虚拟设备上对 Google 助理可见。该测试工具要求将你的应用上传到 Google Play 管理中心内的某个项目。为避免在上传应用时出现“Duplicate package name”错误,请将示例应用的 applicationId 更改为唯一 ID。

app/build.gradle 中,将 applicationId 值“com.example.myapp”更新为唯一 ID,例如“com.codelabs.myname”。如需详细了解 applicationIds,请参阅设置应用 ID。以下代码段展示了更新后的 applicationId

android {
...
    defaultConfig {
        // This ID uniquely identifies your app on the device and in Google Play Store
        applicationId "com.example.myapp"
    ...
    }
}

在 src/res/xml/shortcuts.xml 中,将 android:targetPackage 的所有实例的值更新为唯一的 applicationId。例如:

<capability android:name="actions.intent.GET_THING">
   <intent
       android:action="android.intent.action.VIEW"
       android:targetPackage="com.codelabs.myname"
       ...>
   </intent>
</capability>

在设备上测试应用

在对该应用进行更多更改之前,最好了解一下该示例应用的功能。如需在模拟器上运行该应用,请按以下步骤操作:

  1. 在 Android Studio 中,依次选择“Run”>“Run app”,或点击工具栏中的 Run 图标 Android Studio 中的“Run app”图标
  2. Select Deployment Target 对话框中,选择虚拟设备,然后点击 OK。虽然与应用有关的 Action 在搭载 Android 5(API 级别 21)的设备上也能运行,但我们推荐使用的操作系统版本为 Android 10(API 级别 30)或更高版本。
  3. 长按主屏幕按钮,以设置 Google 助理并验证它能否正常运行。你需要在设备上登录 Google 助理(如果你尚未登录的话)。

如需详细了解 Android 虚拟设备,请参阅创建和管理虚拟设备

快速浏览该应用以了解其功能。点按加号图标可创建新的任务项;借助右上角的菜单项,你可以按完成状态搜索和过滤任务项。

上传到 Play 管理中心

你必须将应用上传到 Google Play 管理中心内的项目,然后才能在 Android Studio 中使用 Google 助理插件。在 Android Studio 中构建您的应用,并将其作为内部版本草稿上传到 Play 管理中心。

如需在 Android Studio 中构建应用,请按以下步骤操作:

  1. 依次点击“Build > Generate Signed Bundle / APK”。
  2. 选择“Android App Bundle”,然后点击“Next”。
  3. 输入详细信息以对应用进行签名,然后点击“Next”。
  4. 选择 prodRelease build 变体,然后点击“Finish”。

Play 管理中心内,请按照以下步骤将你刚刚创建的 app bundle 作为新应用进行上传:

  1. 在“所有应用”页面上,点击创建应用
  2. 随意为该应用命名,然后点击创建应用。在本 Codelab 中,您无需在应用创建完成后填写任何应用信息。
  3. 在边栏菜单中,转到测试并找到 内部测试页面。
  4. 点击内部测试页面上的创建新版本
  5. 点击继续以同意使用 Google Play 应用签名
  6. app bundle 和 APK 面板中,上传您之前生成的 AAB 文件(该文件可能位于 app/prod/release 目录中)。点击保存

4. 创建快捷方式代码库类

示例应用中的多个类将调用 ShortcutManagerCompat API 以推送和管理动态快捷方式。为了减少代码冗余,你将实现一个代码库,让项目类能够轻松管理动态快捷方式。

代码库设计模式提供了一个整洁有序的 API 来管理快捷方式。代码库的优点是底层 API 的细节在最小的 API 后统一抽象。请按照以下步骤来实现代码库:

  1. 创建 ShortcutsRepository 类以抽象化 ShortcutManagerCompat API。
  2. ShortcutsRepository 方法添加到应用的服务定位器
  3. 在主应用中注册 ShortcutRepository 服务。

创建代码库

com.example.android.architecture.blueprints.todoapp.data.source 软件包中创建一个名为 ShortcutsRepository 的新 Kotlin 类。你可以在 app/src/main/java 文件夹中找到此软件包。你将使用该类来实现一个接口,该接口提供了一套涵盖我们 Codelab 用例的基本方法。

显示 ShortcutsRepository 类位置的 Android Studio 窗口。

图 2. Android Studio 中,显示 ShortcutsRepository 类位置的“Project Files”窗口。

将以下代码粘贴到新类中:

package com.example.android.architecture.blueprints.todoapp.data.source

import android.content.Context
import android.content.Intent
import androidx.annotation.WorkerThread
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import com.example.android.architecture.blueprints.todoapp.data.Task
import com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity

private const val GET_THING_KEY = "q"

/**
* ShortcutsRepository provides an interface for managing dynamic shortcuts.
*/
class ShortcutsRepository(val context: Context) {

   private val appContext = context.applicationContext

   /**
    * Pushes a dynamic shortcut. The task ID is used as the shortcut ID.
    * The task's title and description are used as shortcut's short and long labels.
    * The resulting shortcut corresponds to the GET_THING capability with task's
    * title used as BII's "name" argument.
    *
    * @param task Task object for which to create a shortcut.
    */
   @WorkerThread
   fun pushShortcut(task: Task) {
      // TODO
   }

   private fun createShortcutCompat(task: Task): ShortcutInfoCompat {
      //...
   }

   /**
    *  Updates a dynamic shortcut for the provided task. If the shortcut
    *  associated with this task doesn't exist, this method throws an error.
    *  This operation may take a few seconds to complete.
    *
    * @param tasks list of tasks to update.
    */
   @WorkerThread
   fun updateShortcuts(tasks: List<Task>) {
       //...
   }

   /**
    * Removes shortcuts if IDs are known.
    *
    * @param ids list of shortcut IDs
    */
   @WorkerThread
   fun removeShortcutsById(ids: List<String>) {
       //...
   }

   /**
    * Removes shortcuts associated with the tasks.
    *
    * @param tasks list of tasks to remove.
    */
   @WorkerThread
   fun removeShortcuts(tasks: List<Task>) {
       //...
   }
}

接下来,更新 pushShortcut 方法以调用 ShortcutManagerCompat API。请使用以下代码更新 ShortcutsRepository 类:

ShortcutsRepository.kt

/**
* Pushes a dynamic shortcut for the task. The task's ID is used as a shortcut
* ID. The task's title and description are used as shortcut's short and long
* labels. The created shortcut corresponds to GET_THING capability with task's
* title used as BII's "name" argument.
*
* @param task Task object for which to create a shortcut.
*/

@WorkerThread
fun pushShortcut(task: Task) {
   ShortcutManagerCompat.pushDynamicShortcut(appContext, createShortcutCompat(task))
}

在前面的代码示例中,我们已将 appContext 传递给了 API。这是一个包含应用上下文的类属性。若要避免内存泄漏,就一定要使用应用上下文(而不是 activity 上下文),因为上下文的保留期限可能会比宿主 activity 生命周期长。

此外,API 还要求为 Task 对象传递 ShortcutInfoCompat 对象。在前面的代码示例中,我们通过调用 createShortcutCompat 私有方法完成此操作。我们将更新该方法,以创建并返回 ShortcutInfoCompat 对象。为此,请使用以下代码更新 createShortcutCompat 桩:

ShortcutsRepository.kt

private fun createShortcutCompat(task: Task): ShortcutInfoCompat {
   val intent = Intent(appContext, TasksActivity::class.java)
   intent.action = Intent.ACTION_VIEW
   // Filtering is set based on currentTitle.
   intent.putExtra(GET_THING_KEY, task.title)

   // A unique ID is required to avoid overwriting an existing shortcut.
   return ShortcutInfoCompat.Builder(appContext, task.id)
           .setShortLabel(task.title)
           .setLongLabel(task.title)
           // Call addCapabilityBinding() to link this shortcut to a BII. Enables user to invoke a shortcut using its title in Assistant.
           .addCapabilityBinding(
                   "actions.intent.GET_THING", "thing.name", listOf(task.title))
           .setIntent(intent)
           .setLongLived(false)
       .build()
}

此类中的其余函数桩负责更新和删除动态快捷方式。使用以下代码更新这些函数即可启用它们:

ShortcutsRepository.kt

/**
* Updates a Dynamic Shortcut for the task. If the shortcut associated with this task
* doesn't exist, throws an error. This operation may take a few seconds to complete.
*
* @param tasks list of tasks to update.
*/
@WorkerThread
fun updateShortcuts(tasks: List<Task>) {
   val scs = tasks.map { createShortcutCompat(it) }
   ShortcutManagerCompat.updateShortcuts(appContext, scs)
}

/**
* Removes shortcuts if IDs are known.
* @param ids list of shortcut IDs
*/
@WorkerThread
fun removeShortcutsById(ids: List<String>) {
   ShortcutManagerCompat.removeDynamicShortcuts(appContext, ids)
}

/**
* Removes shortcuts associated with the tasks.
*
* @param tasks list of tasks to remove.
*/
@WorkerThread
fun removeShortcuts(tasks: List<Task>) {
   ShortcutManagerCompat.removeDynamicShortcuts (appContext,
           tasks.map { it.id })
}

将类添加到服务定位器

创建 ShortcutsRepository 类后,下一步是将该类的实例化对象提供给应用的其余部分。此应用通过实现服务定位器模式来管理类依赖项。在 Android Studio 中使用类浏览器打开服务定位器类,方法是依次点击 Navigate > Class,然后输入“ServiceLocator”。点击生成的 Kotlin 文件,在 IDE 中将其打开。

ServiceLocator.kt 顶部,粘贴以下代码以导入 ShortcutsRepositorySuppressLint 软件包:

ServiceLocator.kt

package com.example.android.architecture.blueprints.todoapp

// ...Other import statements
import com.example.android.architecture.blueprints.todoapp.data.source.ShortcutsRepository
import android.annotation.SuppressLint

将以下代码粘贴到 ServiceLocator.kt 的正文中,以添加 ShortcutRepository 服务成员和方法:

ServiceLocator.kt

object ServiceLocator {

   // ...
   // Only the code immediately below this comment needs to be copied and pasted
   // into the body of ServiceLocator.kt:

   @SuppressLint("StaticFieldLeak")
   @Volatile
   var shortcutsRepository: ShortcutsRepository? = null

   private fun createShortcutsRepository(context: Context): ShortcutsRepository {
       val newRepo = ShortcutsRepository(context.applicationContext)
       shortcutsRepository = newRepo
       return newRepo
   }

   fun provideShortcutsRepository(context: Context): ShortcutsRepository {
       synchronized(this) {
           return shortcutsRepository ?: shortcutsRepository ?: createShortcutsRepository(context)
       }
   }
 }

注册快捷方式服务

最后一步是向 Application 注册新的 ShortcutsRepository 服务。在 Android Studio 中,打开 TodoApplication.kt 并复制文件顶部附近的以下代码:

TodoApplication.kt

package com.example.android.architecture.blueprints.todoapp
/// ... Other import statements

import com.example.android.architecture.blueprints.todoapp.data.source.ShortcutsRepository

接下来,将以下代码添加到类的正文中,以注册该服务:

TodoApplication.kt

//...
class TodoApplication : Application() {

   //...

   val shortcutsRepository: ShortcutsRepository
       get() = ServiceLocator.provideShortcutsRepository(this)

   //...
}

构建应用并确保其继续运行。

5. 推送新的快捷方式

创建快捷方式服务后,你就可以开始推送快捷方式了。由于用户在此应用中生成内容(任务项),并期望稍后能够返回这些内容,因此,每次用户创建新任务时,我们将通过推送一个绑定到 GET_THING BII 的动态快捷方式来启用对此内容的语音访问。这样,当用户通过说出“Hey Google, open my grocery list on SampleApp”这样的语音指令来触发 BII 后,Google 助理便可以直接启动用户请求的任务项。

你可以按照以下步骤在示例应用中启用此功能:

  1. ShortcutsRepository 服务导入到 AddEditTaskViewModel 类,后者负责管理任务列表对象。
  2. 在用户创建新任务时推送动态快捷方式。

导入 ShortcutsRepository

我们首先需要将 ShortcutsRepository 服务提供给 AddEditTaskViewModel。为此,请将该服务导入到 ViewModelFactory,这是应用用于实例化 ViewModel 对象的工厂类,包括 AddEditTaskViewModel

在 Android Studio 中打开类浏览器,方法是依次点击 Navigate > Class,然后输入“ViewModelFactory”。点击生成的 Kotlin 文件,在 IDE 中将其打开。

ViewModelFactory.kt 顶部,粘贴以下代码以导入 ShortcutsRepositorySuppressLint 软件包:

ViewModelFactory.kt

package com.example.android.architecture.blueprints.todoapp

// ...Other import statements
import com.example.android.architecture.blueprints.todoapp.data.source.ShortcutsRepository

接下来,将 ViewModelFactory 的正文替换为以下代码:

ViewModelFactory.kt

/**
 * Factory for all ViewModels.
 */
@Suppress("UNCHECKED_CAST")
class ViewModelFactory constructor(
    private val tasksRepository: TasksRepository,
    private val shortcutsRepository: ShortcutsRepository,
    owner: SavedStateRegistryOwner,
    defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {

    override fun <T : ViewModel> create(
        key: String,
        modelClass: Class<T>,
        handle: SavedStateHandle
    ) = with(modelClass) {
        when {
            isAssignableFrom(StatisticsViewModel::class.java) ->
                StatisticsViewModel(tasksRepository)
            isAssignableFrom(TaskDetailViewModel::class.java) ->
                TaskDetailViewModel(tasksRepository)
            isAssignableFrom(AddEditTaskViewModel::class.java) ->
                AddEditTaskViewModel(tasksRepository, shortcutsRepository)
            isAssignableFrom(TasksViewModel::class.java) ->
                TasksViewModel(tasksRepository, handle)
            else ->
                throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
        }
    } as T
}

向上移动一层,完成 ViewModelFactory 更改,并将 ShortcutsRepository 传递给工厂的构造函数。依次点击 Navigate > File,然后输入“FragmentExt.kt”,打开 Android Studio 的文件浏览器。点击在 util 软件包中生成的 Kotlin 文件,以在 IDE 中打开它。

FragmentExt.kt 的正文替换为以下代码:

fun Fragment.getViewModelFactory(): ViewModelFactory {
   val taskRepository = (requireContext().applicationContext as TodoApplication).taskRepository
   val shortcutsRepository = (requireContext().applicationContext as TodoApplication).shortcutsRepository
   return ViewModelFactory(taskRepository, shortcutsRepository, this)
}

推送快捷方式

通过为示例应用的 ViewModel 类提供 ShortcutsRepository 抽象类,你可以更新 AddEditTaskViewModel(即负责创建记事的 ViewModel 类),在用户每次创建新记事时推送动态快捷方式。

在 Android Studio 中,打开类浏览器并输入“AddEditTaskViewModel”。点击生成的 Kotlin 文件,在 IDE 中将其打开。

首先,使用以下 import 语句将 ShortcutsRepository 软件包添加到此类:

package com.example.android.architecture.blueprints.todoapp.addedittask

//Other import statements
import com.example.android.architecture.blueprints.todoapp.data.source.ShortcutsRepository

接下来,使用以下代码更新类构造函数,以添加 shortcutsRepository 类属性:

AddEditTaskViewModel.kt

//...

/**
* ViewModel for the Add/Edit screen.
*/
class AddEditTaskViewModel(
   private val tasksRepository: TasksRepository,
   private val shortcutsRepository: ShortcutsRepository
) : ViewModel() {

    //...

添加 ShortcutsRepository 类后,创建一个新函数 pushShortcut() 来调用此类。将以下私有函数粘贴到 AddEditTaskViewModel 的正文中:

AddEditTaskViewModel.kt

//...
private fun pushShortcut(newTask: Task) = viewModelScope.launch {
   shortcutsRepository.pushShortcut(newTask)
}

最后,在每次创建任务时推送一个新的动态快捷方式。将 saveTask() 函数的内容替换为以下代码:

AddEditTaskViewModel.kt

fun saveTask() {
    val currentTitle = title.value
    val currentDescription = description.value

    if (currentTitle == null || currentDescription == null) {
        _snackbarText.value = Event(R.string.empty_task_message)
        return
    }
    if (Task(currentTitle, currentDescription).isEmpty) {
        _snackbarText.value = Event(R.string.empty_task_message)
        return
    }

    val currentTaskId = taskId
    if (isNewTask || currentTaskId == null) {
        val task = Task(currentTitle, currentDescription)
        createTask(task)
        pushShortcut(task)
    } else {
        val task = Task(currentTitle, currentDescription, taskCompleted, currentTaskId)
        updateTask(task)
    }
}

测试代码

我们终于准备好测试代码啦!在此步骤中,你将推送支持语音功能的动态快捷方式,并使用 Google 助理应用对其进行检查。

创建预览

使用 Google 助理插件创建预览后,你的动态快捷方式会出现在测试设备上的 Google 助理中。

安装测试插件

如果你还没有安装 Google 助理插件,请按以下步骤在 Android Studio 中安装该插件:

  1. 依次点击**“File > Settings”(在 MacOS 中,依次点击“Android Studio > Preferences”)。
  2. Plugins 部分中,点击 Marketplace,然后搜索“Google Assistant”。
  3. 安装该工具,然后重启 Android Studio。

创建预览

在 Android Studio 中按照以下步骤创建预览:

  1. 依次点击 Tools > Google Assistant > App Actions Test Tool
  2. App name 框中,指定一个名称,如“Todo List”。
  3. 点击 Create Preview

与应用有关的 Action 测试工具预览创建窗格。

图 3. 与应用有关的 Action 测试工具预览创建窗格。

在测试期间,你推送到 Google 助理的动态快捷方式会出现在 Google 助理中,按你为预览提供的应用名称整理。

推送和检查快捷方式

在测试设备上重新启动示例应用,然后执行以下步骤:

  1. 创建一个标题为“Start codelab”的新任务
  2. 打开 Google 助理应用,然后说出或输入“My shortcuts”。
  3. 点按探索标签页。你应该会看到示例快捷方式。
  4. 点按快捷方式以调用它。你应该会看到,应用启动时,过滤框中预先填充了快捷方式的名称,以便你轻松找到请求的任务项。

6. (可选)更新和删除快捷方式

除了在运行时推送新的动态快捷方式之外,你的应用还可以更新这些快捷方式,以反映用户内容和偏好设置的当前状态。最好在用户修改目标项时(例如重命名示例应用中的任务)更新现有快捷方式。此外,每当移除目标资源时,也应删除相应的快捷方式,以避免向用户显示损坏的快捷方式。

更新快捷方式

修改 AddEditTaskViewModel,以便在用户更改任务项的详细信息时更新动态快捷方式。首先,使用以下代码更新类的正文,以添加利用我们的代码库类的更新函数:

AddEditTaskViewModel.kt

private fun updateShortcut(newTask: Task) = viewModelScope.launch {
   shortcutsRepository.updateShortcuts(listOf(newTask))
}

接下来,修改 saveTask() 函数,以便在现有任务更新时调用我们的新方法。

AddEditTaskViewModel.kt

// Called when clicking on fab.
fun saveTask() {
   // ...
   // Note: the shortcuts are created/updated in a worker thread.
   if (isNewTask || currentTaskId == null) {
       //...
   } else {
       //...
       updateShortcut(task)
   }
}

通过重新启动应用并执行以下步骤来测试你的代码:

  1. 将现有任务项的标题重命名为“Finish codelab”。
  2. 说出“Hey Google, my shortcuts”,打开 Google 助理。
  3. 点按探索标签页。你应该会看到测试快捷方式的更新短标签。

移除快捷方式

每当用户删除任务时,我们都应该移除我们的示例应用快捷方式。在示例应用中,任务删除逻辑位于 TaskDetailViewModel 类中。在更新此类之前,我们需要再次更新 ViewModelFactory,以将 shortcutsRepository 传递到 TaskDetailViewModel

打开 ViewModelFactory 并将其构造函数方法的内容替换为以下代码:

//...
class ViewModelFactory constructor(
       private val tasksRepository: TasksRepository,
       private val shortcutsRepository: ShortcutsRepository,
       owner: SavedStateRegistryOwner,
       defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {

   override fun <T : ViewModel> create(
           key: String,
           modelClass: Class<T>,
           handle: SavedStateHandle
   ) = with(modelClass) {
       when {
           isAssignableFrom(StatisticsViewModel::class.java) ->
               StatisticsViewModel(tasksRepository)
           isAssignableFrom(TaskDetailViewModel::class.java) ->
               TaskDetailViewModel(tasksRepository, shortcutsRepository)
           isAssignableFrom(AddEditTaskViewModel::class.java) ->
               AddEditTaskViewModel(tasksRepository, shortcutsRepository)
           isAssignableFrom(TasksViewModel::class.java) ->
               TasksViewModel(tasksRepository, handle)
           else ->
               throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
       }
   } as T
}

接着,打开 TaskDetailViewModel。使用以下代码导入 ShortcutsRepository 模块并为其声明实例变量:

TaskDetailViewModel.kt

package com.example.android.architecture.blueprints.todoapp.taskdetail

...
import com.example.android.architecture.blueprints.todoapp.data.source.ShortcutsRepository

/**
* ViewModel for the Details screen.
*/
class TaskDetailViewModel(
       //...
       private val shortcutsRepository: ShortcutsRepository
   ) : ViewModel() {
...
}

最后,修改 deleteTask() 函数来调用 shortcutsRepository,以便在删除具有相应 taskId 的任务时根据 ID 来移除快捷方式。

TaskDetailViewModel.kt

fun deleteTask() = viewModelScope.launch {
   _taskId.value?.let {
       //...
       shortcutsRepository.removeShortcutsById(listOf(it))
   }
}

如需测试你的代码,请重新启动该应用并执行以下步骤:

  1. 删除测试任务。
  2. 将现有任务项的标题重命名为“Finish codelab”。
  3. 说出“Hey Google, my shortcuts”,打开 Google 助理。
  4. 点按探索标签页。确认你的测试快捷方式不再出现。

7. 后续步骤

恭喜! 多亏有你,我们示例应用的用户才能通过向 Google 助理发出“Hey Google, open my grocery list on ExampleApp”这样的语音指令,轻松返回到他们创建的记事。快捷方式让用户可以轻松地在应用中再次执行常用操作,从而鼓励用户更深入地互动。

所学内容

在本 Codelab 中,你学习了如何:

  • 确定在应用中推送动态快捷方式的用例。
  • 使用代码库、依赖项注入和服务定位器设计模式降低代码复杂性。
  • 将支持语音功能的动态快捷方式推送到用户生成的应用内容。
  • 更新和移除现有快捷方式。

后续操作

接下来,你可以尝试进一步优化你的任务列表应用。如需参考完成后的项目,请参阅 GitHub 上的代码库的 –codelab-complete 分支

如需进一步了解如何使用与应用有关的 Action 扩展该应用,请参阅以下建议:

如需继续了解 Actions on Google,请浏览下列资源:

欢迎关注我们的 Twitter 帐号 @ActionsOnGoogle,及时了解我们的最新公告,还可以使用标签 #appActions 发布 Twitter 微博,分享你构建的成果!

反馈意见调查

最后,请填写该调查问卷,就您学习本 Codelab 的体验提供反馈意见。