使用 hilt 将 Activity 中的视图模型共享到 compose 函数

Yar*_*rit 5 android android-viewmodel android-jetpack-compose dagger-hilt jetpack-compose-navigation

我的应用程序使用 hilt,我LoadManager在活动内部进行了一些工作,使用读取联系人ContentResolver,当我完成工作时,我得到发送到我的 viewModel 的光标,以便处理数据并执行一些业务逻辑,为此我声明了以下内容在我的活动之上:

@AndroidEntryPoint
class MainActivity : ComponentActivity(), LoaderManager.LoaderCallbacks<Cursor> {
    private val contactsViewModel: ContactsViewModel by viewModels()
 ...
Run Code Online (Sandbox Code Playgroud)

这样我就可以在里面使用它onLoadFinished

    override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor?) {
  
                contactsViewModel.updateContactsListFromCursor(cursor, loader.id)
     }

Run Code Online (Sandbox Code Playgroud)

在我的 viewModel 中,我有以下代码,它使用要显示的联系人更新列表的 ui 状态:

data class ContactsListUiState(
    val contacts: MutableList<Contact>,
    val searchFilter: String)

@HiltViewModel
class ContactsViewModel @Inject constructor() : ViewModel() {
    private val _contactsListUiState =
        MutableStateFlow(ContactsListUiState(mutableStateListOf(), ""))
    val contactsListUiState: StateFlow<ContactsListUiState> = _contactsListUiState.asStateFlow()

    private fun updateContactsList(filter: String) {
        viewModelScope.launch(Dispatchers.IO) {
            ...

            _contactsListUiState.update { currentState ->
                currentState.copy(contacts = list, searchFilter = filter)
            }
        }

Run Code Online (Sandbox Code Playgroud)

最后,我应该显示 a 的联系人,LazyColumn并且我按照官方文档使用 hilt 将 viewModel 传递给我的可组合函数:

@Composable
fun ContactsListScreen(
       navController: NavController,
       modifier: Modifier = Modifier, viewModel: ContactsViewModel = hiltViewModel()
   ) {
       val uiState by viewModel.contactsListUiState.collectAsStateWithLifecycle()
       ...

Run Code Online (Sandbox Code Playgroud)

但是当我访问它时uiState.contacts它是空的,我的列表没有显示任何内容,我还注意到我在活动中使用的 viewModel 实例与我从可组合函数内部contactsViewModel获得的 viewModel 实例不同,这可能会导致此问题。hiltViewModel()

关于如何在活动和可组合函数之间共享相同视图模型的任何建议,假设我必须从 onLoadFinished 函数(不可组合)调用 viewModel,在该函数中我获得光标,因此我必须在活动本身内部有一个 viewModel 引用

z.g*_*g.y 4

基于文档

函数 hiltViewModel() 返回一个现有的 ViewModel 或创建一个新的 ViewModel,其范围仅限于 NavController 返回堆栈上存在的当前导航图。该函数可以选择使用 NavBackStackEntry 将 ViewModel 的范围限制为父返回堆栈条目。

事实证明,当工厂成为导航图的一部分时,它们会创建 ViewModel 的新实例。但由于您已经发现要使其工作,您必须指定,所以我采取了一种基于我最近在这篇文章ViewModelStoreOwner中的答案的方法,并创建了当前活动的 CompositionLocal ,因为它的扩展是它本身。ComponentActivityViewModelStoreOwner

这是我的简短尝试,通过可能的修复来重现您的问题。

活动

@AndroidEntryPoint
class HiltActivityViewModelActivity : ComponentActivity() {

    private val myViewModel: ActivityScopedViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
                CompositionLocalProvider(LocalActivity provides this@HiltActivityViewModelActivity) {
                    Log.e("ActivityScopedViewModel", "Hashcode: ${myViewModel.hashCode()} : Activity Scope")
                    HiltActivitySampleNavHost()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

视图模型

@HiltViewModel
class ActivityScopedViewModel @Inject constructor(): ViewModel() {}
Run Code Online (Sandbox Code Playgroud)

本地活动组合

val LocalActivity = staticCompositionLocalOf<ComponentActivity> {
    error("LocalActivity is not present")
}
Run Code Online (Sandbox Code Playgroud)

简单的导航图

enum class HiltSampleNavHostRoute {
    DES_A, DES_B
}

@Composable
fun HiltActivitySampleNavHost(
    modifier: Modifier = Modifier,
    navController: NavHostController = rememberNavController(),
    startDestination: String = HiltSampleNavHostRoute.DES_A.name
) {
    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {

        composable(HiltSampleNavHostRoute.DES_A.name) {
            DestinationScreenA()
        }

        composable(HiltSampleNavHostRoute.DES_B.name) {
            DestinationScreenB()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

屏幕

// here you can use the Local Activity as the ViewModelStoreOwner
@Composable
fun DestinationScreenA(
    myViewModelParam: ActivityScopedViewModel = hiltViewModel(LocalActivity.current)
    // myViewModelParam: ActivityScopedViewModel = viewModel(LocalActivity.current)
) {
    Log.e("ActivityScopedViewModel", "Hashcode: ${myViewModelParam.hashCode()} : Composable Scope")
}

@Composable
fun DestinationScreenB(
    modifier: Modifier = Modifier
) {}
Run Code Online (Sandbox Code Playgroud)

或者更好的是,就像Phil Dukhov的回答一样,您可以LocalViewModelStoreOwner在调用构建器时用作参数。

相同的导航主机

@Composable
fun HiltActivitySampleNavHost(
    ...
) {

    val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    }

    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {

        composable(HiltSampleNavHostRoute.DES_A.name) {
            DestinationScreenA(
                myViewModelParam = viewModel(viewModelStoreOwner)
            )
        }

        ...
    }
}


Run Code Online (Sandbox Code Playgroud)

导航图中的活动和可组合项的日志都显示相同的哈希码

E/ActivityScopedViewModel: Hashcode: 267094635 : Activity Scope
E/ActivityScopedViewModel: Hashcode: 267094635 : Composable Scope
Run Code Online (Sandbox Code Playgroud)

另请参阅色雷斯人的回答。它有关于 的非常详细的解释ComponentActivity,并且基于它,我认为我提出的第一个解决方案可能适合您的情况。