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

1. ภาพรวม

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

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

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

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

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

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

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

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

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

  • เทอร์มินัลเพื่อเรียกใช้คำสั่งเชลล์ที่ติดตั้ง 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"
  • ทางลัดที่เชื่อมโยงกับ START_EXERCISE BII จะช่วยให้ผู้ใช้ดูเซสชันการออกกำลังกายของตนเองได้ * "Ok Google บอก ExampleApp ให้เริ่มออกกำลังกายตามปกติของฉัน"

ดูรายการ BII ทั้งหมดที่จัดหมวดหมู่แล้วได้ที่ข้อมูลอ้างอิงของ Intent ในตัว

การให้ทางลัดแก่ 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. ในกล่องโต้ตอบ Welcome to Android Studio ให้คลิก Import project
  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ด้วยชื่อที่ไม่ซ้ำกันของคุณ

เพิ่มการอ้างอิง 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 > Run app หรือคลิก Runเรียกใช้ไอคอนแอปใน Android Studio ในแถบเครื่องมือ
  2. ในกล่องโต้ตอบเลือกเป้าหมายการติดตั้งใช้งาน ให้เลือกอุปกรณ์ แล้วคลิกตกลง เวอร์ชันระบบปฏิบัติการที่แนะนำคือ Android 10 (API ระดับ 30) ขึ้นไป แม้ว่า App Actions จะทำงานในอุปกรณ์ที่ใช้ 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 คุณจะใช้คลาสนี้เพื่อติดตั้งอินเทอร์เฟซที่มีชุดเมธอดขั้นต่ำซึ่งครอบคลุมกรณีการใช้งานของโค้ดแล็บ

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

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

วางโค้ดต่อไปนี้ลงในคลาสใหม่

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 เพื่อเรียกใช้ API ShortcutManagerCompat อัปเดต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 นี่คือพร็อพเพอร์ตี้ของคลาสที่เก็บ Application Context การใช้ Application Context (ไม่ใช่ Activity Context) เป็นสิ่งสำคัญเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำ เนื่องจากบริบทอาจคงอยู่นานกว่าวงจรของกิจกรรมโฮสต์

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

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

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

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

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

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

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ให้เสร็จสิ้นโดยขึ้นไป 1 เลเยอร์ แล้วส่งไปยังตัวสร้างของโรงงาน เปิดเบราว์เซอร์ไฟล์ของ Android Studio โดยไปที่นำทาง > ไฟล์ แล้วพิมพ์ "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)
}

ส่งทางลัด

เมื่อมี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. ไปที่ **File > Settings (Android Studio > Preferences ใน MacOS)
  2. ในส่วนปลั๊กอิน ให้ไปที่มาร์เก็ตเพลส แล้วค้นหา "Google Assistant"
  3. ติดตั้งเครื่องมือแล้วรีสตาร์ท Android Studio

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

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

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

แผงสร้างตัวอย่างเครื่องมือทดสอบ App Actions

รูปที่ 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. เปลี่ยนชื่อรายการงานที่มีอยู่เป็น "ทำ 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. เปลี่ยนชื่อรายการงานที่มีอยู่เป็น "ทำ Codelab ให้เสร็จ"
  3. เปิด Google Assistant โดยพูดว่า "Ok Google ทางลัดของฉัน"
  4. แตะแท็บสำรวจ ตรวจสอบว่าทางลัดทดสอบไม่ปรากฏอีกต่อไป

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

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

สิ่งที่เราได้พูดถึง

ในโค้ดแล็บนี้ คุณได้เรียนรู้วิธีทำสิ่งต่อไปนี้

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

ขั้นต่อไปคืออะไร

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

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

หากต้องการเดินทางต่อใน Actions on Google โปรดดูแหล่งข้อมูลต่อไปนี้

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

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

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