Jetpack Compose跨页面传值问题:ViewModel跳转后值为空
解决Jetpack Compose多页面ViewModel数据丢失问题
问题根源
你遇到的问题本质是ViewModel生命周期与导航页面绑定:默认通过viewModel()或hiltViewModel()获取的ViewModel,生命周期和当前导航目的地的NavBackStackEntry绑定。当你跳转到最后一页时,如果前两个页面的条目被从回退栈移除(比如使用popUpTo并设置inclusive = true),对应的NavBackStackEntry会被销毁,ViewModel也会随之清除,数据自然为空。
解决方案
方案1:使用共享ViewModel(推荐)
创建一个绑定到父级生命周期(如宿主Activity或根导航图)的共享ViewModel,让三个页面共用同一个实例,避免页面跳转时ViewModel被销毁。
步骤:
- 创建包含所有页面所需数据的共享ViewModel:
class SharedQrCreateViewModel : ViewModel() { // 第一页数据状态 val page1Data = mutableStateOf("") // 第二页数据状态 val page2Data = mutableStateOf("") // 可添加其他共享状态或业务方法 }
在三个页面中获取同一个共享ViewModel实例:
- 若使用Hilt,先给ViewModel添加
@HiltViewModel注解,再通过宿主Activity获取实例:
val sharedViewModel = hiltViewModel<SharedQrCreateViewModel>( viewModelStoreOwner = LocalContext.current as AppCompatActivity )- 不使用Hilt时,通过宿主的
ViewModelStoreOwner获取:
val activityStoreOwner = LocalContext.current as ViewModelStoreOwner val sharedViewModel = viewModel<SharedQrCreateViewModel>(activityStoreOwner)- 若使用Hilt,先给ViewModel添加
修改前两个页面的逻辑,将数据存入共享ViewModel:
比如第一页输入完成后执行sharedViewModel.page1Data.value = inputText,第二页同理更新page2Data。最后一页直接读取这两个状态值即可。
方案2:导航时传递数据
如果数据结构简单,可直接通过导航组件的参数传递数据到最后一页,无需依赖前两个页面的ViewModel。
步骤:
- 在导航图中定义带参数的目的地:
fun NavGraphBuilder.qrCreateGraph(navController: NavController) { composable("qr_create_1") { QrCreate1Screen(navController) } composable( "qr_create_2/{page1Data}", arguments = listOf(navArgument("page1Data") { type = NavType.StringType }) ) { backStackEntry -> val page1Data = backStackEntry.arguments?.getString("page1Data") ?: "" QrCreate2Screen(navController, page1Data) } composable( "qr_create_final/{page1Data}/{page2Data}", arguments = listOf( navArgument("page1Data") { type = NavType.StringType }, navArgument("page2Data") { type = NavType.StringType } ) ) { backStackEntry -> val page1Data = backStackEntry.arguments?.getString("page1Data") ?: "" val page2Data = backStackEntry.arguments?.getString("page2Data") ?: "" QrCreateFinalScreen(page1Data, page2Data) } }
- 页面跳转时传递参数:
第一页跳转到第二页:
navController.navigate("qr_create_2/${page1InputText}")
第二页跳转到最后一页:
navController.navigate("qr_create_final/${page1Data}/${page2InputText}")
- 最后一页直接接收参数并使用即可。
方案3:持久化临时数据
如果数据复杂且需要跨进程/重启保留,可使用DataStore或Room临时存储数据,最后一页读取后再清理。
示例(使用DataStore):
- 创建DataStore实例:
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "qr_temp_data")
- 定义存储键:
val PAGE1_DATA_KEY = stringPreferencesKey("page1_data") val PAGE2_DATA_KEY = stringPreferencesKey("page2_data")
- 前两个页面保存数据:
context.dataStore.edit { preferences -> preferences[PAGE1_DATA_KEY] = inputText }
- 最后一页读取数据:
val page1Data = context.dataStore.data .map { it[PAGE1_DATA_KEY] ?: "" } .collectAsState(initial = "") val page2Data = context.dataStore.data .map { it[PAGE2_DATA_KEY] ?: "" } .collectAsState(initial = "")
注意事项
- 优先选择方案1,符合ViewModel的设计理念,能轻松处理复杂状态和业务逻辑。
- 若用导航参数传递复杂数据,需将数据类实现
Parcelable或Serializable,并在导航参数中指定对应类型。 - 避免使用单例ViewModel,除非明确需要全局唯一状态,否则易引发内存泄漏或状态混乱。
内容的提问来源于stack exchange,提问作者David Fernandes
相关产品推荐
相关产品推荐

