1. 개요
이전 Codelab에서는 정적 바로가기를 사용하여 흔히 사용되는 내장 인텐트(BII)를 샘플 앱에 구현했습니다. Android 개발자는 앱 작업을 사용하여 앱 기능을 Google 어시스턴트로 확장합니다.
정적 바로가기는 앱과 함께 번들로 제공되며, 앱의 새 버전을 출시해야만 업데이트가 가능합니다. 사용자 제작 콘텐츠와 같은 앱의 동적 요소에 음성 기능을 사용 설정하려면 동적 바로가기를 사용해야 합니다. 앱은 사용자가 관련 작업(예: 작업 추적 앱에서 새 메모 만들기)을 실행하면 동적 바로가기를 푸시합니다. 앱 작업을 사용하면 개발자는 이러한 바로가기를 BII에 결합하여 음성으로 사용하도록 설정할 수 있고, 이를 통해 사용자는 "Hey Google, ExampleApp에서 내 식료품 목록 열어줘"와 같이 말하여 어시스턴트에서 콘텐츠에 액세스할 수 있습니다.
그림 1. 사용자가 만든 작업 항목과 더불어 Google 어시스턴트가 해당 작업 항목에 대한 동적 바로가기를 실행하는 모습을 보여주는 세 단계의 화면
빌드할 내용
이 Codelab에서는 사용자가 앱에서 직접 만든 작업 목록 항목을 열도록 어시스턴트에 요청할 수 있도록 샘플 할 일 목록 Android 앱에서 음성의 동적 바로가기를 사용 설정합니다. 이를 위해 Android 아키텍처 패턴, 특히 저장소, 서비스 로케이터, ViewModel 패턴을 사용합니다.
기본 요건
이 Codelab은 이전 Codelab에서 다룬 앱 작업 개념, 특히 BII 및 정적 바로가기를 기반으로 합니다. 앱 작업을 처음 사용하는 경우 계속하기 전에 해당 Codelab을 완료하는 것이 좋습니다.
또한 계속하기 전에 개발 환경에 다음과 같은 구성이 갖춰져 있는지 확인합니다.
- git이 설치된 셸 명령어를 실행하는 터미널
- Android 스튜디오의 최신 안정화 버전
- 인터넷 액세스가 가능한 실제 또는 가상 Android 기기
- Android 스튜디오, Google 앱, Google 어시스턴트 앱에 로그인된 Google 계정
2. 작동 방식 이해
음성 액세스에 동적 바로가기를 사용 설정하려면 다음을 따르세요.
- 동적 바로가기를 대상 BII에 연결
- Google 바로가기 통합 라이브러리를 추가하여 어시스턴트가 바로가기를 처리할 수 있도록 지원
- 사용자가 관련 인앱 작업을 완료할 때마다 바로가기 푸시
바로가기 연결
어시스턴트에서 동적 바로가기에 액세스할 수 있으려면 관련 BII에 연결된 상태여야 합니다. 바로가기가 연결된 BII가 트리거되면 어시스턴트는 사용자 요청의 매개변수를 연결된 바로가기에 정의된 키워드에 매칭합니다. 예를 들면 다음과 같습니다.
GET_THING
BII에 연결된 바로가기를 통해 사용자는 어시스턴트에서 직접 특정 인앱 콘텐츠를 요청할 수 있습니다. * "Hey Google, ExampleApp에서 내 식료품 목록 열어줘."ORDER_MENU_ITEM
BII에 연결된 바로가기를 통해 사용자는 이전 주문을 재현할 수 있습니다. * "Hey Google, ExampleApp에서 평소대로 주문해 줘."
범주화된 BII 전체 목록은 내장 인텐트 참조를 참고하세요.
어시스턴트에 바로가기 제공
바로가기를 BII에 연결한 다음에는 Google 바로가기 통합 라이브러리를 프로젝트에 추가하여 어시스턴트가 이러한 바로가기를 처리하도록 사용 설정해야 합니다. 이 라이브러리를 사용하면 어시스턴트는 앱에서 푸시한 각 바로가기를 인식하기 때문에 사용자는 어시스턴트에서 바로가기의 트리거 문구를 사용하여 이러한 바로가기를 실행할 수 있습니다.
3. 개발 환경 준비
이 Codelab에서는 Android용으로 빌드된 샘플 할 일 목록 앱을 사용합니다. 이 앱을 사용하면 사용자는 목록에 항목을 추가하고, 카테고리별로 작업 목록 항목을 검색하고, 완료 상태를 기준으로 작업을 필터링할 수 있습니다. 이 섹션을 완료하여 샘플 앱을 다운로드하고 준비합니다.
기본 파일 다운로드
다음 명령어를 실행하여 샘플 앱의 GitHub 저장소를 클론합니다.
git clone https://github.com/actions-on-google/app-actions-dynamic-shortcuts.git
저장소를 클론한 후에는 다음 단계를 따라 Android 스튜디오에서 엽니다.
- Welcome to Android Studio 대화상자에서 Import project를 클릭합니다.
- 저장소를 클론한 폴더를 선택합니다.
또는 GitHub 저장소의 codelab-complete
브랜치를 클론하여 완료된 Codelab을 나타내는 샘플 앱 버전을 볼 수 있습니다.
git clone https://github.com/actions-on-google/app-actions-dynamic-shortcuts.git --branch codelab-complete
Android 애플리케이션 ID 업데이트
앱의 애플리케이션 ID를 업데이트하면 테스트 기기에서 앱을 고유하게 식별할 수 있으며, 앱이 Play Console에 업로드되어도 '패키지 이름 중복' 오류가 발생하지 않도록 방지할 수 있습니다. 애플리케이션 ID를 업데이트하려면 app/build.gradle
을 엽니다.
android {
...
defaultConfig {
applicationId "com.MYUNIQUENAME.android.fitactions"
...
}
}
applicationId
필드의 'MYUNIQUENAME'을 고유한 값으로 바꿉니다.
바로가기 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'
...
}
기기에서 앱 테스트
앱에 추가 변경사항을 적용하기 전에 샘플 앱의 기능을 파악하면 도움이 됩니다. 에뮬레이터에서 앱을 실행하려면 다음 단계를 따르세요.
- Android 스튜디오에서 Run > Run app을 선택하거나 툴바에서 Run을 클릭합니다.
- Select Deployment Target 대화상자에서 기기를 선택하고 OK를 클릭합니다. 작업은 Android 5(API 수준 21)에서도 실행되지만, Android 10(API 수준 30) 이상의 OS 버전을 사용하는 것이 좋습니다.
- 홈 버튼을 길게 눌러 어시스턴트를 설정하고 작동하는지 확인합니다. 아직 로그인하지 않았다면 기기에서 어시스턴트에 로그인해야 합니다.
Android 가상 기기에 대한 자세한 내용은 가상 기기 만들기 및 관리하기를 참고하세요.
앱을 간단히 탐색하여 기능을 확인합니다. 더하기 아이콘을 탭하면 새 작업 항목이 생성되며, 오른쪽 상단의 메뉴 항목을 통해 완료 상태를 기준으로 작업 항목을 검색하고 필터링할 수 있습니다.
4. 바로가기 저장소 클래스 만들기
샘플 앱의 여러 클래스는 ShortcutManagerCompat
API를 호출하여 동적 바로가기를 푸시하고 관리합니다. 코드 중복을 줄이기 위해 저장소를 구현하여 프로젝트 클래스가 동적 바로가기를 손쉽게 관리할 수 있도록 합니다.
저장소 디자인 패턴은 바로가기를 관리하기 위한 깔끔한 API를 제공합니다. 저장소의 장점은 기본 API의 세부정보가 최소 API 뒤에 균일하게 추상화된다는 점입니다. 다음 단계에 따라 저장소를 구현합니다.
ShortcutsRepository
클래스를 만들어ShortcutManagerCompat
API를 추상화합니다.ShortcutsRepository
메서드를 앱의 서비스 로케이터에 추가합니다.- 기본 애플리케이션에
ShortcutRepository
서비스를 등록합니다.
저장소 만들기
com.example.android.architecture.blueprints.todoapp.data.source
패키지에 ShortcutsRepository
라는 새 Kotlin 클래스를 만듭니다. 이 패키지는 app/src/main/java
폴더에 정리되어 있습니다. 이 클래스를 사용하여 Codelab 사용 사례를 다루는 최소한의 메서드 집합을 제공하는 인터페이스를 구현할 것입니다.
그림 2. ShortcutsRepository 클래스의 위치를 표시하는 Android 스튜디오의 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에 전달했습니다. 이는 애플리케이션 컨텍스트가 포함된 클래스 속성입니다. 컨텍스트가 호스트 활동 수명 주기보다 오래 유지될 수 있으므로, 메모리 누수를 방지하기 위해 활동 컨텍스트가 아닌 애플리케이션 컨텍스트를 사용해야 합니다.
또한 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 스튜디오의 Navigate > Class에서 클래스 브라우저를 열어 '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
다음 코드를 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)
}
}
}
바로가기 서비스 등록
마지막은 새 ShortcutsRepository
서비스를 애플리케이션에 등록하는 것입니다. Android 스튜디오에서 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, SampleApp에서 내 식료품 목록 열어줘"와 같이 말하여 BII를 트리거할 때 사용자가 요청한 작업 항목으로 바로 실행할 수 있습니다.
다음 단계를 완료하여 샘플 앱에서 이 기능을 사용 설정하세요.
ShortcutsRepository
서비스를 작업 목록 객체 관리를 담당하는AddEditTaskViewModel
클래스로 가져옵니다.- 사용자가 새 작업을 만들 때 동적 바로가기를 푸시합니다.
ShortcutsRepository 가져오기
먼저 AddEditTaskViewModel
에 ShortcutsRepository
서비스를 제공해야 합니다. 이를 위해 앱에서 AddEditTaskViewModel
을 비롯하여 ViewModel 객체를 인스턴스화하는 데 사용하는 팩토리 클래스인 ViewModelFactory
에 서비스를 가져옵니다.
Android 스튜디오의 Navigate > Class에서 클래스 브라우저를 열어 '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
를 팩토리의 생성자에 전달합니다. Android 스튜디오의 Navigate > File에서 파일 브라우저를 열고 'FragmentExt.kt'를 입력합니다. 결과로 반환되고 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
추상화 클래스를 통해 메모를 만드는 ViewModel
클래스인 AddEditTaskViewModel
을 업데이트하여 사용자가 새 메모를 만들 때마다 동적 바로가기를 푸시합니다.
Android 스튜디오에서 클래스 브라우저를 열고 '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 어시스턴트 앱을 사용하여 이러한 바로가기를 검사합니다.
미리보기 만들기
Google 어시스턴트 플러그인을 사용하여 미리보기를 만들면 테스트 기기의 어시스턴트에 동적 바로가기가 표시됩니다.
테스트 플러그인 설치
아직 Google 어시스턴트 플러그인이 없는 경우 다음 단계에 따라 Android 스튜디오에서 설치합니다.
- **File > Settings(MacOS의 경우 Android Studio > Preferences)로 이동합니다.
- Plugins 섹션에서 Marketplace로 이동하여 'Google Assistant'를 검색합니다.
- Marketplace에서 플러그인을 찾을 수 없는 경우에는 수동으로 플러그인을 다운로드하고 디스크에서 플러그인 설치 안내를 따릅니다.
- 도구를 설치하고 Android 스튜디오를 다시 시작합니다.
미리보기 만들기
다음 단계를 따라 Android 스튜디오에서 미리보기를 만듭니다.
- Tools > Google Assistant > 'App Actions Test Tool'을 클릭합니다.
- App name 상자에 '할 일 목록'과 같이 이름을 정의합니다.
- Create Preview를 클릭합니다. 메시지가 표시되면 앱 작업 정책 및 서비스 약관을 검토하고 이에 동의합니다.
그림 3. App Actions Test Tool의 미리보기 만들기 창
테스트 중에 어시스턴트로 푸시한 동적 바로가기가 미리보기에 제공한 앱 이름별로 정리된 어시스턴트에 표시됩니다.
바로가기 푸시 및 검사
테스트 기기에서 샘플 앱을 다시 실행하고 다음 단계를 수행하세요.
- 'Start Codelab'이라는 제목의 새 작업을 만듭니다.
- Google 어시스턴트 앱을 열고 '내 바로가기'라고 말하거나 입력합니다.
- 탐색 탭을 탭합니다. 샘플 바로가기가 표시됩니다.
- 바로가기를 탭하여 실행합니다. 필터 상자에 바로가기 이름이 자동 입력된 상태로 앱이 실행되어야 요청된 작업 항목을 쉽게 찾을 수 있습니다.
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)
}
}
코드를 테스트하려면 앱을 다시 실행하고 다음 단계를 따르세요.
- 기존 작업 항목의 이름을 'Finish Codelab'으로 바꿉니다.
- "Hey Google, 내 바로가기"라고 말하여 Google 어시스턴트를 엽니다.
- 탐색 탭을 탭합니다. 테스트 바로가기에 대해 업데이트된 짧은 라벨이 표시됩니다.
바로가기 삭제
샘플 앱 바로가기는 사용자가 작업을 삭제할 때마다 삭제해야 합니다. 샘플 앱에서는 작업 삭제 로직이 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() {
...
}
마지막으로 이에 상응하는 taskId
가 있는 작업이 삭제될 때마다 해당 ID를 토대로 바로가기를 삭제하기 위해 shortcutsRepository
를 호출하도록 deleteTask()
함수를 수정합니다.
TaskDetailViewModel.kt
fun deleteTask() = viewModelScope.launch {
_taskId.value?.let {
//...
shortcutsRepository.removeShortcutsById(listOf(it))
}
}
코드를 테스트하려면 앱을 다시 실행하고 다음 단계를 따르세요.
- 테스트 작업을 삭제합니다.
- 기존 작업 항목의 이름을 'Finish Codelab'으로 바꿉니다.
- "Hey Google, 내 바로가기"라고 말하여 Google 어시스턴트를 엽니다.
- 탐색 탭을 탭합니다. 테스트 바로가기가 더 이상 표시되지 않는지 확인합니다.
7. 다음 단계
축하합니다. 개발자 여러분 덕분에 샘플 앱 사용자가 어시스턴트에 "Hey Google, ExampleApp에서 내 식료품 목록 열어줘"와 같이 말하여 사용자가 직접 만든 메모로 쉽게 돌아갈 수 있습니다. 바로가기는 사용자가 앱에서 자주 사용하는 작업을 쉽게 재현할 수 있도록 함으로써 심층적인 사용자 참여를 유도합니다.
학습한 내용
이 Codelab을 통해 학습한 내용은 다음과 같습니다.
- 앱에서 동적 바로가기를 푸시하는 사용 사례를 파악하는 방법
- 저장소, 종속 항목 삽입, 서비스 로케이터 디자인 패턴을 사용하여 코드 복잡성을 줄이는 방법
- 사용자 제작 앱 콘텐츠에 음성 지원 동적 바로가기를 푸시하는 방법
- 기존 바로가기를 업데이트하고 삭제하는 방법
다음 단계
이제 작업 목록 앱을 추가로 조정해 볼 수 있습니다. 완료된 프로젝트를 참조하려면 GitHub의 저장소 –codelab-complete 브랜치를 참고하세요.
다음은 앱 작업으로 이 앱을 확장하는 방법을 자세히 알아보기 위한 몇 가지 제안사항입니다.
- Firebase용 Google 애널리틱스를 사용한 할 일 목록 샘플에서 앱 작업의 성능을 추적하는 방법을 알아보세요.
- 앱 작업 내장 인텐트 참조에서 앱을 어시스턴트로 확장하는 다양한 방법을 알아보세요.
Actions on Google 과정을 계속하려면 다음 리소스를 살펴보세요.
- actions.google.com: Actions on Google의 공식 문서 사이트
- 앱 작업 샘플 색인: 앱 작업 기능을 살펴보기 위한 샘플 앱 및 코드
- Actions on Google GitHub 저장소: 샘플 코드 및 라이브러리
- r/GoogleAssistantDev: Google 어시스턴트로 작업하는 개발자를 위한 공식 Reddit 커뮤니티
트위터에서 @ActionsOnGoogle을 팔로우하여 최신 소식을 확인하고 #appActions로 트윗을 보내 직접 빌드한 결과물을 공유하세요.
의견 설문조사
마지막으로 이 설문조사를 작성하여 이 Codelab 사용 경험에 관한 의견을 보내주세요.