Jetpack Compose NavHost二次重组导致MVVM+Hilt场景下请求重复调用问题求解
问题根源
- 你定义的
planeInfo()是普通成员方法,每次调用都会执行planeInfoUseCase.execute()生成新的Flow实例,再转为新的LiveData实例,每次新的LiveData被观察都会触发一次数据流启动 - NavHost的
composable代码块会随导航状态变化、宿主页面重组触发多次执行,每次执行都会调用PlanesScreen内的viewModel.planeInfo()生成新的LiveData,最终触发多次重复请求
修复方案
第一步:将数据流改为ViewModel成员变量,仅初始化一次
ViewModel的生命周期和页面绑定,只要页面未出栈销毁,ViewModel实例就不会重建,成员变量仅会初始化一次,从根源避免重复生成数据流:
@HiltViewModel class PlanesViewModel @Inject constructor(private val planeInfoUseCase: PlaneInfoUseCase) : ViewModel() { // 改为成员变量,ViewModel实例化时仅执行一次数据请求 val planeInfo: LiveData<UiResponse<ArrayList<PlaneInfo>>> = planeInfoUseCase.execute().toUiResponse().flowAsLiveData(Dispatchers.IO) }
第二步:调整PlanesScreen调用逻辑,直接访问成员变量
不需要再调用方法生成新的LiveData,直接观察固定的成员变量即可,同时可以直接在PlanesScreen内部获取Hilt注入的ViewModel,不需要上层传参:
@Composable fun PlanesScreen(viewModel : PlanesViewModel = hiltViewModel()){ PlaneList(viewModel.planeInfo.observeAsState()) }
第三步:简化Navigation层代码
不需要在NavHost的composable块中传入ViewModel,完全不需要把ViewModel往上透传给Navigation函数:
@Composable fun Navigation(navController: NavHostController){ NavHost(navController, startDestination = NavigationItem.Planes.route) { composable(NavigationItem.Planes.route) { // 无需传参,ViewModel在PlanesScreen内部获取 PlanesScreen() } composable(NavigationItem.Weather.route) { WeatherScreen() } composable(NavigationItem.More.route) { MoreScreen() } } }
可选优化(非必须)
你可以省去Flow转LiveData的环节,直接在Compose中通过collectAsStateWithLifecycle收集Flow数据,更适配Compose生命周期,性能表现更好:
// ViewModel中直接返回Flow val planeInfo: Flow<UiResponse<ArrayList<PlaneInfo>>> = planeInfoUseCase.execute().toUiResponse().flowOn(Dispatchers.IO) // Compose中直接收集 @Composable fun PlanesScreen(viewModel : PlanesViewModel = hiltViewModel()){ val state by viewModel.planeInfo.collectAsStateWithLifecycle(initialValue = UiResponse.Loading()) PlaneList(state) }
内容的提问来源于stack exchange,提问作者Shindra
相关产品推荐
相关产品推荐

