[Android] 019. ViewModel(lifecycle-viewmodel-compose:2.5.0)

最近のgoogleのサンプルでContainerクラスを作ってそこでRepositoryのインスタンスを作成し、ApplicationクラスでContainerのインスタンスを作成してViewでViewModelにRepositoryを渡す実装がいくつかありました

手動による依存関係挿入でコンテナを使用した依存関係の管理ってやつですね
https://developer.android.com/training/dependency-injection/manual?hl=ja#dependencies-container

気がついてなかったのですがandroidx.lifecycle:lifecycle-*:2.5.0で以下の機能が追加されていました

lifecycle-viewmodel-compose がラムダ ファクトリを受け取る viewModel() API を提供するようになりました。これにより、カスタムの ViewModelProvider.Factory を作成せずに、ViewModel インスタンスを作成できるようになります。
Lifecycle  |  Android デベロッパー  |  Android Developers

というわけでシンプルな構成のViewModelの実装をしてみます
この構成だとApplicationクラスやContextをRepositoryで使いたい場合でもコンテナで吸収できるのでViewModelをAndroidViewModelにする必要がなくなるのが利点です
※ 推奨事項でAndroidViewModelを使用が非推奨になってました…

AndroidViewModel ではなく ViewModel クラスを使用します。Application クラスは ViewModel で使用しないようにします。代わりに、依存関係を UI またはデータレイヤに移行します。
Android アーキテクチャに関する推奨事項  |  Android デベロッパー  |  Android Developers

Project Structureでandroidx.lifecycle:lifecycle-viewmodel-composeの2.5.0以上をimplementationします(現時点で安定版は2.6.1で2.7.0-alpha01が最新です)

確認用に適当なデータを実装します

data class Item(
    val id: Int = 0,
    val name: String,
)

class UserItems {
    companion object {
        val items = listOf<Item>(
            Item(0, "item 0"),
            Item(1, "item 1"),
            Item(2, "item 2"),
            Item(3, "item 3"),
            Item(4, "item 4"),
        )
    }
}

データを取得するRepositoryを実装

interface ItemsRepository {
    fun getAll(): List<Item>
}

class UserItemsRepository : ItemsRepository {
    override fun getAll(): List<Item> = UserItems.items
}

ViewModelも実装していきます

class ItemsViewModel(
    private val repo: ItemsRepository,
) : ViewModel() {
    val items = repo.getAll()
}

Repositoryのインスタンスを作成するコンテナを実装

interface AppContainer {
    val itemsRepository: ItemsRepository
}

class AppDataContainer() : AppContainer {
    override val itemsRepository: ItemsRepository by lazy {
        UserItemsRepository()
    }
}

Applicationクラスを実装してコンテナのインスタンスを作成

/**
 * ```AndroidManifest.xml
 * <application
 *     android:name=".Application"
 * ```
 */
class Application: android.app.Application() {
    lateinit var container: AppContainer

    override fun onCreate() {
        super.onCreate()

        container = AppDataContainer()
    }
}

画面をdeveloperの説明を参考に実装

@Composable
fun ItemScreen(
    itemsViewModel: ItemsViewModel = viewModel {
        val application = checkNotNull(get(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY) as Application)
        ItemsViewModel(application.container.itemsRepository)
    }
) {
    LazyColumn(Modifier.fillMaxSize()) {
        itemsIndexed(itemsViewModel.items) { index, item ->
            ListItem(headlineContent = { Text("${item.id}: ${item.name}") },)
        }
    }
}

Android Studio Giraffe 2022.3.1 built on June 29, 2023