使用 Compose UI 时如何为每个列表项创建单独的 ViewModel?

Die*_*mar 2 android mvvm viewmodel android-architecture-components android-jetpack-compose

我正在开发一个交易应用程序。我需要列出用户股票及其价值(利润或损失)以及投资组合的总价值。

对于馆藏列表,在 MVP 架构中,我将为每个列表项创建一个演示者,但对于此应用程序,我决定使用 MVVM(Compose、ViewModels 和 Hilt)。我的第一个想法是为每个列表项创建一个不同的 ViewModel。我hiltViewModel()在可组合方法签名中使用来创建 ViewModel 的实例,但这总是给我相同的实例,而这不是我想要的。使用 MVVM 架构时,我正在尝试以正确的方式执行操作还是应该使用单个 ViewModel?您知道我可以看看的任何项目吗?下图是我的实际屏幕的超级简化,每个单元格都很复杂,这就是为什么我想为每个单元格使用不同的 ViewModel。任何建议都非常受欢迎。

在此输入图像描述

Phi*_*hov 5

Hilt不支持键控视图模型。Compose 中存在对键控视图模型的功能请求,但我们必须等到 Hilt 支持它。

这是一个关于如何暂时绕过它的黑客解决方案。

您可以创建一个可与键一起使用的普通视图模型,并通过 Hilt 视图模型将注入传递到该视图模型:

class SomeInjection @Inject constructor() {
    val someValue = 0
}

@HiltViewModel
class InjectionsProvider @Inject constructor(
    val someInjection: SomeInjection
): ViewModel() {

}

class SomeViewModel(private val injectionsProvider: InjectionsProvider) : ViewModel() {
    val injectedValue get() = injectionsProvider.someInjection.someValue
    var storedValue by mutableStateOf("")
        private set

    fun updateStoredValue(value: String) {
        storedValue = value
    }
}

@Composable
fun keyedViewModel(key: String) : SomeViewModel {
    val injectionsProvider = hiltViewModel<InjectionsProvider>()
    return viewModel(
        key = key,
        factory = object: ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                @Suppress("UNCHECKED_CAST")
                return SomeViewModel(injectionsProvider) as T
            }

        }
    )
}

@Composable
fun TestScreen(
) {
    LazyColumn {
        items(100) { i ->
            val viewModel = keyedViewModel("$i")
            Text(viewModel.injectedValue.toString())
            TextField(value = viewModel.storedValue, onValueChange = viewModel::updateStoredValue)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Hilt 2.43 发布后更新

您可以创建如下函数:

class SomeInjection @Inject constructor() {
    val someValue = 0
}

@HiltViewModel
class InjectionsProvider @Inject constructor(
    val someInjection: SomeInjection
): ViewModel() {

}

class SomeViewModel(private val injectionsProvider: InjectionsProvider) : ViewModel() {
    val injectedValue get() = injectionsProvider.someInjection.someValue
    var storedValue by mutableStateOf("")
        private set

    fun updateStoredValue(value: String) {
        storedValue = value
    }
}

@Composable
fun keyedViewModel(key: String) : SomeViewModel {
    val injectionsProvider = hiltViewModel<InjectionsProvider>()
    return viewModel(
        key = key,
        factory = object: ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                @Suppress("UNCHECKED_CAST")
                return SomeViewModel(injectionsProvider) as T
            }

        }
    )
}

@Composable
fun TestScreen(
) {
    LazyColumn {
        items(100) { i ->
            val viewModel = keyedViewModel("$i")
            Text(viewModel.injectedValue.toString())
            TextField(value = viewModel.storedValue, onValueChange = viewModel::updateStoredValue)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.HiltViewModelFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry

@Composable
inline fun <reified VM : ViewModel> hiltViewModel(key: String): VM {
    val viewModelStoreOwner =
        if (checkNotNull(LocalViewModelStoreOwner.current) is NavBackStackEntry) {
            checkNotNull(LocalViewModelStoreOwner.current) { "ViewModelStoreOwner is null" }
        } else null

    return viewModel(
        key = key,
        factory = if (viewModelStoreOwner is NavBackStackEntry) {
            HiltViewModelFactory(
                LocalContext.current,
                viewModelStoreOwner
            )
        } else null
    )
}
Run Code Online (Sandbox Code Playgroud)