如何通过Hilt在Activity与Compose函数间共享同一ViewModel实例
解决Activity与Compose共享同一Hilt ViewModel实例的问题
问题根源
默认情况下,在NavHost包裹的Compose屏幕中调用hiltViewModel()时,ViewModel会绑定到NavBackStackEntry(导航栈条目)作为ViewModelStoreOwner;而Activity中通过by viewModels()获取的ViewModel是绑定到Activity本身的。两者作用域不同,因此会生成两个独立的ViewModel实例,导致状态无法同步。
解决方案:指定Compose使用Activity作为ViewModelStoreOwner
既然需要保留Activity中的ViewModel引用,直接在Compose组件中指定ViewModelStoreOwner为当前Activity,就能复用Activity中创建的ViewModel实例。
步骤1:修改Activity代码,传递ViewModelStoreOwner到Compose
@AndroidEntryPoint class ContactsActivity : AppCompatActivity() { // 保留Activity中的ViewModel引用 private val viewModel: ContactsViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 初始化联系人Loader逻辑 loaderManager.initLoader(CONTACTS_LOADER_ID, null, contactsLoaderCallbacks) setContent { ContactsAppTheme { // 将Activity自身作为ViewModelStoreOwner传递给Compose屏幕 ContactsListScreen(viewModelStoreOwner = this) } } } private val contactsLoaderCallbacks = object : LoaderManager.LoaderCallbacks<Cursor> { override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // 实现联系人Loader创建逻辑 return CursorLoader( this@ContactsActivity, ContactsContract.Contacts.CONTENT_URI, arrayOf(ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY), null, null, "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} ASC" ) } override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) { // 将Cursor传递给ViewModel处理 viewModel.processContactsCursor(data) } override fun onLoaderReset(loader: Loader<Cursor>) { // 重置Cursor相关资源 viewModel.clearContacts() } } companion object { private const val CONTACTS_LOADER_ID = 1001 } }
步骤2:修改Compose组件,使用传入的ViewModelStoreOwner获取ViewModel
@Composable fun ContactsListScreen( viewModelStoreOwner: ViewModelStoreOwner ) { // 通过指定的ViewModelStoreOwner获取与Activity共享的ViewModel实例 val viewModel: ContactsViewModel = hiltViewModel(viewModelStoreOwner) // 收集StateFlow状态,确保生命周期安全 val uiState by viewModel.contactsListUiState.collectAsStateWithLifecycle() LazyColumn(modifier = Modifier.fillMaxSize()) { if (uiState.isLoading) { item { Text(text = "加载中...", modifier = Modifier.padding(16.dp)) } } else { items(uiState.contacts) { contact -> Text(text = contact.name, modifier = Modifier.padding(16.dp)) } } } } // 示例联系人数据类与UI状态 data class Contact(val id: String, val name: String) data class ContactsListUiState( val isLoading: Boolean = true, val contacts: List<Contact> = emptyList() )
步骤3:确保ViewModel使用@HiltViewModel注解
@HiltViewModel class ContactsViewModel @Inject constructor() : ViewModel() { private val _contactsListUiState = MutableStateFlow(ContactsListUiState()) val contactsListUiState: StateFlow<ContactsListUiState> = _contactsListUiState.asStateFlow() fun processContactsCursor(cursor: Cursor?) { val contacts = mutableListOf<Contact>() cursor?.use { while (it.moveToNext()) { val id = it.getString(it.getColumnIndexOrThrow(ContactsContract.Contacts._ID)) val name = it.getString(it.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)) contacts.add(Contact(id, name)) } } _contactsListUiState.value = _contactsListUiState.value.copy( isLoading = false, contacts = contacts ) } fun clearContacts() { _contactsListUiState.value = ContactsListUiState() } }
关键说明
ViewModelStoreOwner是ViewModel的作用域载体,Activity实现了该接口,指定它作为owner后,Compose会复用Activity中已创建的ViewModel实例。- 使用
collectAsStateWithLifecycle()替代普通的collectAsState(),可以确保在Activity进入后台时自动停止收集状态,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Yarin Shitrit
相关产品推荐
相关产品推荐

