#69 Jetpack ComposeでToDoアプリを作る - HiltとViewModel
今回はHiltとViewModelをアプリに導入してみます。
ViewModel
ViewModelはもともとAndroid Architecture Componentのひとつとして発表されました。
- 画面(Screen)に関するデータの保持と操作を担当する
- Configuration Changeでもインスタンスが保持される
- スコープ(画面)から外れた時点で破棄される
という特徴をもちます。Configuration Changeでもインスタンスが保持されるという特徴は便利なので、今回も使ってみます。
Hilt
Hiltは最近公式でおすすめされている依存性注入(DI)ライブラリです。Daggerが元になっています。 先ほど紹介したViewModelに対し、依存するオブジェクトを注入するのに使うととても便利なのでこちらもセットで使ってみます。
ライブラリの追加
まずはViewModelを追加します。
dependencies {
...
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"
}
次にHiltです。ルートのbuild.gradleにclasspathを追加します。
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.36'
}
}
app/build.gradleでは、まずプラグインを追加します。(追記)kaptも追加します。。。
plugins {
...
id 'dagger.hilt.android.plugin'
id 'kotlin-kapt'
}
そしてdependenciesでHiltを追加します。Navigation
Composeも使っているのでhilt-navigation-compose
も追加します。
dependencies {
...
implementation "com.google.dagger:hilt-android:2.36"
// kspだと執筆時点では動作しませんでした。。。
kapt "com.google.dagger:hilt-android-compiler:2.36"
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0-alpha03'
}
Hiltの準備
HiltにDIしてもらうための準備をいくつかやります。Viewシステムのときとやることは同じです。
まず、Applicationクラスを作ります。
package com.mokelab.compose.todo
@HiltAndroidApp
class ToDoApplication : Application()
そしてAndroidManifest.xmlにも登録しておきます。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mokelab.compose.todo">
<application
...
android:name=".ToDoApplication">
<activity>
<intent-filter></intent-filter>
</activity>
</application>
</manifest>
次にMainActivityに@AndroidEntryPoint
アノテーションをつけます。
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
...
}
最後にモジュールを作ります。Room部分のオブジェクトの作り方だけ教えておきましょう。
@Module
@InstallIn(SingletonComponent::class)
object MainModule {
@Provides
@Singleton
fun provideToDoDatabase(@ApplicationContext context: Context): ToDoDatabase {
return Room.databaseBuilder(
context,
ToDoDatabase::class.java,
"todo.db"
).build()
}
@Provides
@Singleton
fun provideToDoDAO(db: ToDoDatabase): ToDoDAO {
return db.todoDAO()
}
}
各画面用のViewModelを作る
Hiltの準備ができたら各画面用のViewModelを作ります。中身は必要になったときに実装していきましょう。
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel()
@HiltViewModel
class CreateToDoViewModel @Inject constructor() : ViewModel()
@HiltViewModel
class ToDoDetailViewModel @Inject constructor() : ViewModel()
@HiltViewModel
class EditToDoViewModel @Inject constructor() : ViewModel()
クラスに@HiltViewModel
を、コンストラクタに@Inject
をつけます。
パラメータにViewModelを追加する
各画面のComposable関数の引数にViewModelを追加します。
@Composable
fun MainScreen(
navController: NavController,
viewModel: MainViewModel,
) { ... }
@Composable
fun CreateToDoScreen(
navController: NavController,
viewModel: CreateToDoViewModel,
) { ... }
@Composable
fun ToDoDetailScreen(
navController: NavController,
viewModel: ToDoDetailViewModel,
todoId: Int
) { ... }
@Composable
fun EditToDoScreen(
navController: NavController,
viewModel: EditToDoViewModel,
todoId: Int,
) { ... }
ViewModelインスタンスを取得する
hiltViewModel()
を使って、NavHostの箇所でViewModelインスタンスを取得し、各画面のComposableに渡します。
NavHost(navController = navController, startDestination = "main") {
composable("main") {
val viewModel = hiltViewModel<MainViewModel>()
MainScreen(navController = navController, viewModel = viewModel)
}
composable("create") {
val viewModel = hiltViewModel<CreateToDoViewModel>()
CreateToDoScreen(navController = navController, viewModel = viewModel)
}
composable(
"detail/{todoId}",
arguments = listOf(navArgument("todoId") { type = NavType.IntType })
) { backStackEntry ->
val viewModel = hiltViewModel<ToDoDetailViewModel>()
val todoId = backStackEntry.arguments?.getInt("todoId") ?: 0
ToDoDetailScreen(
navController = navController,
viewModel = viewModel,
todoId = todoId
)
}
composable(
"edit/{todoId}",
arguments = listOf(navArgument("todoId") { type = NavType.IntType })
) { backStackEntry ->
val viewModel = hiltViewModel<EditToDoViewModel>()
val todoId = backStackEntry.arguments?.getInt("todoId") ?: 0
EditToDoScreen(
navController = navController,
viewModel = viewModel,
todoId = todoId,
)
}
}
これで、HiltにDIしてもらったViewModelを各画面に渡せるようになりました。
ここまで作業したものはこちらにあります。