ขยายทางลัดแบบไดนามิกไปยัง Google Assistant ด้วยการดำเนินการของแอป

1. ภาพรวม

ใน Codelab ก่อนหน้า คุณได้ใช้แป้นพิมพ์ลัดแบบคงที่เพื่อนำ Intent ในตัว (BII) ที่ใช้กันโดยทั่วไปในแอปตัวอย่าง นักพัฒนาแอป Android ใช้การดำเนินการของแอปเพื่อเพิ่มฟังก์ชันการทำงานของแอปให้กับ Google Assistant

ทางลัดแบบคงที่จะมาพร้อมกับแอปและจะอัปเดตได้โดยการเผยแพร่แอปเวอร์ชันใหม่เท่านั้น คุณจะเปิดใช้ฟังก์ชันเสียงสำหรับองค์ประกอบแบบไดนามิกในแอป เช่น เนื้อหาที่ผู้ใช้สร้างขึ้นได้โดยใช้ทางลัดแบบไดนามิก แอปจะพุชทางลัดแบบไดนามิกหลังจากที่ผู้ใช้ดำเนินการที่เกี่ยวข้อง เช่น สร้างโน้ตใหม่ในแอปติดตามงาน เมื่อใช้การดำเนินการของแอป คุณจะเปิดใช้ทางลัดเหล่านี้เพื่อใช้เสียงได้โดยเชื่อมโยงกับ BII ซึ่งช่วยให้ผู้ใช้เข้าถึงเนื้อหาจาก Assistant ได้โดยพูดคำสั่ง เช่น "Ok Google เปิดรายการซื้อของใน ExampleApp"

หน้าจอแบบโพรเกรสซีฟ 3 หน้าจอที่แสดงภาพ Google Assistant กำลังเปิดใช้งานทางลัดแบบไดนามิก

รูปที่ 1 หน้าจอแบบโพรเกรสซีฟ 3 หน้าจอแสดงงานที่ผู้ใช้สร้างขึ้น และ Google Assistant เปิดใช้ทางลัดแบบไดนามิกไปยังรายการงานนั้น

สิ่งที่คุณจะสร้าง

ใน Codelab นี้ คุณจะเปิดใช้ทางลัดแบบไดนามิกสำหรับเสียงในแอป Android สำหรับรายการสิ่งที่ต้องทำได้ ซึ่งจะช่วยให้ผู้ใช้ขอให้ Assistant เปิดรายการงานที่ตนสร้างในแอปได้ คุณดำเนินการนี้ได้โดยใช้รูปแบบสถาปัตยกรรม Android โดยเฉพาะรูปแบบ repository, service locator และ ViewModel

ข้อกำหนดเบื้องต้น

Codelab นี้สร้างขึ้นจากแนวคิด App Actions ที่พูดถึงใน Codelab ก่อนหน้านี้ โดยเฉพาะ BII และทางลัดแบบคงที่ หากคุณเพิ่งเริ่มใช้การดําเนินการของแอป เราขอแนะนําให้ทํา Codelab ให้เสร็จสิ้นก่อนดำเนินการต่อ

นอกจากนี้ โปรดตรวจสอบว่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์ของคุณมีการกำหนดค่าต่อไปนี้ก่อนดำเนินการต่อ

  • เทอร์มินัลสำหรับเรียกใช้คำสั่ง Shell ที่ติดตั้ง git
  • Android Studio เวอร์ชันเสถียรล่าสุด
  • อุปกรณ์ Android จริงหรือเสมือนที่มีการเข้าถึงอินเทอร์เน็ต
  • บัญชี Google ที่ลงชื่อเข้าใช้ Android Studio, แอป Google และแอป Google Assistant

2. ทำความเข้าใจวิธีการทำงาน

การเปิดใช้งานทางลัดแบบไดนามิกสำหรับการเข้าถึงด้วยเสียงเกี่ยวข้องกับขั้นตอนต่อไปนี้:

  • เชื่อมโยงทางลัดแบบไดนามิกกับ BII ที่มีสิทธิ์
  • เปิดใช้ Assistant เพื่อนําเข้าทางลัดโดยเพิ่มไลบรารีการผสานรวมทางลัดของ Google
  • กดทางลัดเมื่อใดก็ตามที่ผู้ใช้ทำงานที่เกี่ยวข้องในแอปจนเสร็จสิ้น

แป้นพิมพ์ลัดการเชื่อมโยง

หากต้องการเข้าถึงทางลัดแบบไดนามิกจาก Assistant ทางลัดนั้นจะต้องเชื่อมโยงกับ BII ที่เกี่ยวข้อง เมื่อมีการทริกเกอร์ BII ที่มีทางลัด Assistant จะจับคู่พารามิเตอร์ในคำขอของผู้ใช้กับคีย์เวิร์ดที่กำหนดไว้ในทางลัดที่เชื่อมโยง เช่น

  • แป้นพิมพ์ลัดที่เชื่อมโยงกับ GET_THING BII ช่วยให้ผู้ใช้ขอเนื้อหาที่เจาะจงในแอปจาก Assistant ได้โดยตรง * "Ok Google เปิดรายการซื้อของใน ExampleApp"
  • แป้นพิมพ์ลัดที่เชื่อมโยงกับ ORDER_MENU_ITEM BII ทำให้ผู้ใช้เล่นคำสั่งซื้อก่อนหน้าซ้ำได้ * "Ok Google สั่งอาหารตามปกติจาก ExampleApp"

โปรดดูข้อมูลอ้างอิง Intent ในตัวเพื่อดูรายการ BII ที่จัดหมวดหมู่ทั้งหมด

การส่งทางลัดให้กับ Assistant

หลังจากเชื่อมโยงทางลัดกับ BII แล้ว ขั้นตอนถัดไปคือให้ Assistant นำเข้าทางลัดเหล่านี้โดยเพิ่มไลบรารีการผสานรวมทางลัดของ Google ลงในโปรเจ็กต์ของคุณ เมื่อใช้ไลบรารีนี้ Assistant จะทราบถึงทางลัดแต่ละรายการที่แอปของคุณพุช ช่วยให้ผู้ใช้เปิดทางลัดเหล่านั้นได้โดยใช้วลีทริกเกอร์ของทางลัดใน Assistant

3. เตรียมสภาพแวดล้อมในการพัฒนาซอฟต์แวร์

Codelab นี้ใช้ตัวอย่างแอปรายการสิ่งที่ต้องทำที่สร้างขึ้นสำหรับ Android แอปนี้จะช่วยให้ผู้ใช้สามารถเพิ่มรายการต่างๆ ค้นหารายการงานตามหมวดหมู่ และกรองงานตามสถานะงานที่ทำเสร็จแล้วได้ ดาวน์โหลดและเตรียมแอปตัวอย่างโดยกรอกข้อมูลในส่วนนี้ให้ครบถ้วน

ดาวน์โหลดไฟล์พื้นฐาน

เรียกใช้คำสั่งต่อไปนี้เพื่อโคลนที่เก็บ GitHub ของแอปตัวอย่าง

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

เมื่อโคลนที่เก็บแล้ว ให้ทำตามขั้นตอนต่อไปนี้เพื่อเปิดที่เก็บใน Android Studio

  1. คลิกนำเข้าโปรเจ็กต์ในกล่องโต้ตอบยินดีต้อนรับสู่ Android Studio
  2. เลือกโฟลเดอร์ที่คุณโคลนที่เก็บ

หรือคุณอาจดูเวอร์ชันของแอปตัวอย่างที่แสดงถึง Codelab ที่สมบูรณ์โดยการโคลนสาขา codelab-complete ของที่เก็บ GitHub ดังนี้

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

อัปเดตรหัสแอปพลิเคชัน Android

การอัปเดตรหัสแอปพลิเคชันของแอปจะระบุแอปบนอุปกรณ์ทดสอบโดยไม่ซ้ำกัน และหลีกเลี่ยงไม่ให้มี "ชื่อแพ็กเกจซ้ำ" หากอัปโหลดแอปไปยัง Play Console หากต้องการอัปเดตรหัสแอปพลิเคชัน ให้เปิด app/build.gradle:

android {
...
  defaultConfig {
    applicationId "com.MYUNIQUENAME.android.fitactions"
    ...
  }
}

แทนที่ "MYUNIQUENAME" ในช่อง applicationId เพื่อนำเสนอข้อมูลเฉพาะสำหรับคุณ

เพิ่มทรัพยากร Dependency ของ 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'
   ...
}

ทดสอบแอปในอุปกรณ์

ก่อนทำการเปลี่ยนแปลงอื่นๆ กับแอป ควรเห็นภาพว่าแอปตัวอย่างทำอะไรได้บ้าง หากต้องการเรียกใช้แอปบนโปรแกรมจำลอง ให้ทำตามขั้นตอนต่อไปนี้

  1. ใน Android Studio เลือก Run > เรียกใช้แอปหรือคลิกเรียกใช้เรียกใช้ไอคอนแอปใน Android Studioในแถบเครื่องมือ
  2. เลือกอุปกรณ์ในกล่องโต้ตอบเลือกเป้าหมายการทำให้ใช้งานได้ แล้วคลิกตกลง เวอร์ชันระบบปฏิบัติการที่แนะนำคือ Android 10 (API ระดับ 30) ขึ้นไป แต่การดำเนินการของแอปจะทำงานในอุปกรณ์ตั้งแต่ Android 5 (API ระดับ 21) ได้
  3. กดปุ่มหน้าแรกค้างไว้เพื่อตั้งค่า Assistant และยืนยันว่าใช้งานได้ คุณจะต้องลงชื่อเข้าใช้ Assistant ในอุปกรณ์ หากยังไม่ได้ดำเนินการ

ดูข้อมูลเพิ่มเติมเกี่ยวกับอุปกรณ์เสมือนของ Android ได้ที่สร้างและจัดการอุปกรณ์เสมือน

สำรวจแอปคร่าวๆ เพื่อดูว่าแอปทำอะไรได้บ้าง การแตะไอคอนบวกจะเป็นการสร้างรายการงานใหม่ และรายการในเมนูที่ด้านขวาบนจะช่วยให้คุณค้นหาและกรองรายการงานตามสถานะเสร็จสมบูรณ์ได้

4. สร้างคลาสที่เก็บทางลัด

หลายคลาสในแอปตัวอย่างของเราจะเรียกใช้ ShortcutManagerCompat API เพื่อพุชและจัดการทางลัดแบบไดนามิก หากต้องการลดความซ้ำซ้อนของโค้ด คุณจะใช้ที่เก็บเพื่อเปิดใช้คลาสของโปรเจ็กต์เพื่อให้จัดการทางลัดแบบไดนามิกได้โดยง่าย

รูปแบบการออกแบบที่เก็บมี API ที่ดูสะอาดตาสำหรับการจัดการทางลัด ข้อได้เปรียบของที่เก็บคือรายละเอียดของ API ที่สำคัญจะอยู่ใน API ที่น้อยที่สุดในแบบเดียวกัน ใช้งานที่เก็บโดยทำตามขั้นตอนต่อไปนี้

  1. สร้างคลาส ShortcutsRepository เพื่อกำหนด API ของ ShortcutManagerCompat
  2. เพิ่มเมธอด ShortcutsRepository ลงในเครื่องระบุตำแหน่งบริการของแอป
  3. ลงทะเบียนบริการ ShortcutRepository ในแอปพลิเคชันหลัก

สร้างที่เก็บ

สร้างคลาส Kotlin ใหม่ชื่อ ShortcutsRepository ในแพ็กเกจ com.example.android.architecture.blueprints.todoapp.data.source แพ็กเกจนี้จะอยู่ในโฟลเดอร์ "app/src/main/java" คุณจะใช้คลาสนี้เพื่อติดตั้งอินเทอร์เฟซที่มีชุดเมธอดเพียงเล็กน้อยสำหรับกรณีการใช้งาน Codelab

หน้าต่าง Android Studio ที่แสดงตำแหน่งของคลาส ShortcutsRepository

รูปที่ 2 หน้าต่างไฟล์โปรเจ็กต์ Android Studio ที่แสดงตำแหน่งของคลาส UTF-Repository

วางรหัสต่อไปนี้ในชั้นเรียนใหม่

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 นี่เป็นพร็อพเพอร์ตี้คลาสที่มีบริบทแอปพลิเคชัน คุณจะต้องใช้บริบทของแอปพลิเคชัน (ต่างจากบริบทกิจกรรม) เพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ เนื่องจากบริบทอาจถูกเก็บรักษาไว้นานกว่าวงจรของกิจกรรมของโฮสต์

นอกจากนี้ API ยังกำหนดให้เราส่งออบเจ็กต์ ShortcutInfoCompat สำหรับออบเจ็กต์ Task ในตัวอย่างโค้ดก่อนหน้านี้ เราดำเนินการนี้ได้ด้วยการเรียกเมธอดส่วนตัว 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 แล้ว ขั้นตอนถัดไปคือทำให้ส่วนที่เหลือของแอปสามารถใช้ออบเจ็กต์ที่สร้างอินสแตนซ์ของคลาสนี้ได้ แอปนี้จัดการทรัพยากร Dependency ของคลาสโดยใช้รูปแบบตัวระบุตำแหน่งบริการ เปิดคลาสเครื่องระบุตำแหน่งบริการโดยใช้เบราว์เซอร์คลาสใน Android Studio โดยไปที่นำทาง > คลาส และพิมพ์ "ServiceLocator" คลิกไฟล์ Kotlin ที่ได้เพื่อเปิดใน IDE

วางรหัสต่อไปนี้เพื่อนำเข้าแพ็กเกจ ShortcutsRepository และ SuppressLint ที่ด้านบนของ ServiceLocator.kt

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

เพิ่มสมาชิกบริการ ShortcutRepository และวิธีการต่างๆ โดยวางโค้ดต่อไปนี้ลงในเนื้อหาของ ServiceLocator.kt:

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

ลงทะเบียนบริการทางลัด

ขั้นตอนสุดท้ายคือการลงทะเบียนบริการ 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 ทุกครั้งที่ผู้ใช้สร้างงานใหม่ วิธีนี้ช่วยให้ Assistant เปิดตัวผู้ใช้ไปยังรายการงานที่ขอได้โดยตรงเมื่อเรียกใช้ BII โดยถามสิ่งต่างๆ อย่างเช่น "Ok Google เปิดรายการสิ่งที่ต้องซื้อใน SampleApp"

คุณสามารถเปิดใช้ฟังก์ชันนี้ในแอปตัวอย่างโดยทำตามขั้นตอนต่อไปนี้

  1. นำเข้าบริการ ShortcutsRepository ไปยังคลาส AddEditTaskViewModel ซึ่งมีหน้าที่จัดการออบเจ็กต์รายการงาน
  2. พุชทางลัดแบบไดนามิกเมื่อผู้ใช้สร้างงานใหม่

นำเข้าที่เก็บทางลัด

ก่อนอื่นเราจำเป็นต้องทำให้บริการ ShortcutsRepository พร้อมใช้งานสำหรับ AddEditTaskViewModel หากต้องการดำเนินการดังกล่าว ให้นำเข้าบริการไปยัง ViewModelFactory ซึ่งเป็นคลาสเริ่มต้นที่แอปใช้ในการสร้างอินสแตนซ์ ViewModel ซึ่งรวม AddEditTaskViewModel ด้วย

เปิดเบราว์เซอร์ของชั้นเรียนใน Android Studio โดยไปที่นำทาง > คลาส และพิมพ์ "ViewModelfactor" คลิกไฟล์ Kotlin ที่ได้เพื่อเปิดใน IDE

วางรหัสต่อไปนี้เพื่อนำเข้าแพ็กเกจ ShortcutsRepository และ SuppressLint ที่ด้านบนของ ViewModelFactory.kt

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 ให้เสร็จโดยเลื่อนขึ้น 1 ชั้น แล้วส่ง ShortcutsRepository ไปยังเครื่องมือสร้างของโรงงาน เปิดเบราว์เซอร์ไฟล์ของ Android Studio โดยไปที่นำทาง > File และพิมพ์ "FragmentExt.kt" คลิกไฟล์ Kotlin ที่ได้ในแพ็กเกจ util เพื่อเปิดใน 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)
}

พุชทางลัด

เมื่อคลาส Abstraction ของ ShortcutsRepository พร้อมใช้งานกับคลาส ViewModel ของแอปตัวอย่าง คุณจะอัปเดต AddEditTaskViewModel ซึ่งเป็นคลาส ViewModel ที่มีหน้าที่สร้างโน้ต ให้พุชทางลัดแบบไดนามิกทุกครั้งที่ผู้ใช้สร้างโน้ตใหม่

ใน Android Studio ให้เปิดเบราว์เซอร์ของชั้นเรียน แล้วพิมพ์ "AddEditTaskViewModel" คลิกไฟล์ Kotlin ที่ได้เพื่อเปิดใน IDE

ขั้นแรก ให้เพิ่มแพ็กเกจ 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 Assistant

สร้างตัวอย่างเพลง

การสร้างตัวอย่างโดยใช้ปลั๊กอิน Google Assistant จะทำให้ทางลัดแบบไดนามิกปรากฏใน Assistant ในอุปกรณ์ทดสอบ

ติดตั้งปลั๊กอินทดสอบ

หากยังไม่มีปลั๊กอิน Google Assistant ให้ติดตั้งโดยทำตามขั้นตอนต่อไปนี้ใน Android Studio

  1. ไปที่ **ไฟล์ > การตั้งค่า (Android Studio > ค่ากำหนดบน MacOS)
  2. ในส่วนปลั๊กอิน ให้ไปที่ Marketplace และค้นหา "Google Assistant"
  3. ติดตั้งเครื่องมือและรีสตาร์ท Android Studio

สร้างตัวอย่าง

สร้างตัวอย่างโดยทำตามขั้นตอนเหล่านี้ใน Android Studio

  1. คลิกเครื่องมือ > Google Assistant > "App Actions Test Tool"
  2. ในช่องชื่อแอป ให้กำหนดชื่อ เช่น "รายการสิ่งที่ต้องทำ"
  3. คลิกสร้างพรีวิว หากระบบถาม ให้อ่านและยอมรับนโยบายและข้อกำหนดในการให้บริการของการดำเนินการของแอป

แผงการสร้างตัวอย่างของเครื่องมือทดสอบการดำเนินการของแอป

รูปที่ 3 แผงการสร้างตัวอย่างของเครื่องมือทดสอบการดำเนินการของแอป

ในระหว่างการทดสอบ ทางลัดแบบไดนามิกที่พุชไปยัง Assistant จะปรากฏใน Assistant ซึ่งจัดเรียงตามชื่อแอปที่คุณให้ไว้สำหรับการแสดงตัวอย่าง

พุชและตรวจสอบทางลัด

เปิดแอปตัวอย่างอีกครั้งในอุปกรณ์ทดสอบ แล้วทำตามขั้นตอนต่อไปนี้

  1. สร้างงานใหม่ที่มีชื่อว่า "เริ่ม Codelab"
  2. เปิดแอป Google Assistant แล้วพูดหรือพิมพ์ว่า "ทางลัดของฉัน"
  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. เปิด Google Assistant โดยพูดว่า "Ok 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 ที่สอดคล้องกันออก

TaskDetailViewModel.kt

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

หากต้องการทดสอบโค้ด ให้เปิดแอปอีกครั้งและทำตามขั้นตอนต่อไปนี้

  1. ลบงานทดสอบ
  2. เปลี่ยนชื่อรายการงานที่มีอยู่เป็น "Finish Codelab"
  3. เปิด Google Assistant โดยพูดว่า "Ok Google ทางลัดของฉัน"
  4. แตะแท็บสำรวจ ตรวจสอบว่าทางลัดการทดสอบไม่ปรากฏขึ้นอีกแล้ว

7. ขั้นตอนถัดไป

ยินดีด้วย ขอขอบคุณที่ช่วยให้ผู้ใช้แอปตัวอย่างของเรากลับไปดูโน้ตที่สร้างได้ง่ายๆ โดยถาม Assistant เกี่ยวกับสิ่งต่างๆ อย่างเช่น "Ok Google เปิดรายการซื้อของใน ExampleApp" ทางลัดจะกระตุ้นให้ผู้ใช้มีส่วนร่วมมากขึ้นโดยทำให้ผู้ใช้เล่นการดำเนินการที่ใช้บ่อยซ้ำในแอปได้ง่ายๆ

หัวข้อที่ครอบคลุม

คุณได้เรียนรู้วิธีต่อไปนี้ใน Codelab แล้ว

  • ระบุกรณีการใช้งานการพุชทางลัดแบบไดนามิกในแอป
  • ลดความซับซ้อนของโค้ดโดยใช้รูปแบบการออกแบบที่เก็บ การแทรกทรัพยากร และรูปแบบเครื่องระบุตำแหน่งบริการ
  • พุชทางลัดแบบไดนามิกที่ใช้เสียงได้ไปยังเนื้อหาแอปที่ผู้ใช้สร้างขึ้น
  • อัปเดตและนำทางลัดที่มีอยู่ออก

สิ่งที่ต้องทำต่อไป

จากที่นี่ คุณสามารถลองปรับเกณฑ์การค้นหาเพิ่มเติมในแอปรายการงานได้ หากต้องการอ้างอิงโปรเจ็กต์ที่เสร็จสมบูรณ์แล้ว ให้ดูที่เก็บ –codelab-complete Branch ใน GitHub

ต่อไปนี้คือคำแนะนำบางส่วนสำหรับข้อมูลเพิ่มเติมเกี่ยวกับการขยายแอปนี้ด้วยการดำเนินการของแอป

โปรดสำรวจแหล่งข้อมูลเหล่านี้เพื่อดำเนินการต่อในเส้นทางของ Actions on Google

ติดตามเราบน Twitter @ActionsOnGoogle เพื่อติดตามประกาศล่าสุดของเรา และทวีตไปที่ #appActions เพื่อแชร์สิ่งที่คุณสร้าง

แบบสำรวจความคิดเห็น

สุดท้ายนี้ โปรดกรอกแบบสำรวจนี้เพื่อแสดงความคิดเห็นเกี่ยวกับประสบการณ์ในการใช้งาน Codelab นี้