如何在 jetpack compose 中将一个视图模型用于两个可组合屏幕?

How can I use one viewmodel for two composable screen in jetpack compose?

提问人:NewPartizal 提问时间:10/26/2023 更新时间:10/28/2023 访问量:47

问:

我有一个名为AddNutritionSearchRoute的屏幕,在这个屏幕上有用户选择的食物,例如苹果、梨、西瓜等。此屏幕上有一个列表按钮。当用户按下列表按钮时,他会转到屏幕以列出名为 SelectedItemListRoute 的选定项目,用户可以在此处删除他选择的项目,例如苹果、梨、西瓜列表。让它删除苹果并返回 AddNutritionSearchRoute 屏幕。再次说到这个屏幕,我需要使用相同的列表,即梨和西瓜必须保留在列表中,因为苹果已被删除。不断移动列表似乎很荒谬,一定有一种更简单的方法。我的代码如下

AddNutritionSearchRoute

@Composable
fun AddNutritionSearchRoute(
    navHostController: NavHostController,
    mealId:Int?,
    viewModel: AddNutritionSearchViewModel = hiltViewModel()


...

AddNutritionSearchViewModel 视图模型

@HiltViewModel
class AddNutritionSearchViewModel @Inject constructor(
   ...
) : ViewModel() {


    private val _selectedNtrItem = MutableStateFlow(SelectedNtrItem())
    val selectedNtrItem: StateFlow<SelectedNtrItem> = _selectedNtrItem.asStateFlow()

    fun deleteItemFromList(item: NutritionSearchItem) {
        _selectedNtrItem.value.items.remove(item)
    }

    fun addSelectedNutritionItem(
        ntrItem: NutritionSearchItem,
        selectedNtrItem: ArrayList<NutritionSearchItem>
    ) {
        if (selectedNtrItem.contains(ntrItem)) {
            selectedNtrItem.remove(ntrItem)
        } else {
            selectedNtrItem.add(ntrItem)
        }
    }
   ... 

SelectedItemList屏幕

如果我以这种方式定义如下所示的视图模型,并通过视图模型访问列表,则列表将显示为空。即使我选择了一个元素,列表也可能是空的,因为我再次定义了视图模型。

@Composable
fun SelectedItemListRoute(
    navHostController: NavHostController,
    list: SelectedNtrItem?
    viewModel: AddNutritionSearchViewModel = hiltViewModel(), 
) {

    SelectedItemListScreen(navHostController,list?.items ?: arrayListOf() )
}

    @OptIn(ExperimentalMaterialApi::class)
    @Composable
    fun SelectedItemListScreen(
        navHostController: NavHostController,
        list: ArrayList<NutritionSearchItem>
    
    ) {
    
        val scope = rememberCoroutineScope()
    
        val modalBottomSheetState =
            rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
    
        ModalBottomSheetLayout(
            sheetContent = {
                BaseBottomSheet(
                    content = {
                        DeleteItemConfirmation(
                            onConfirmClick = {
    
                                scope.launch {
                                    modalBottomSheetState.hide()
                                }
    
                            },
                            onCancelClick = {
                                scope.launch {
                                    modalBottomSheetState.hide()
                                }
                            }
                        )
                    }
                )
            },
            sheetState = modalBottomSheetState,
            sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
        ) {
    
            Scaffold(topBar = {
                SelectedItemListTopBar(
                    onClickCancel = {
                        navHostController.popBackStack()
                    },
                    listSize = list.size
                )
            }) { padding ->
                LazyColumn(modifier = Modifier.padding(15.dp)) {
                    items(list) { item ->
                        BaseNtrItem(
                            ntrId = item.nutritionId,
                            image = item.image,
                            ntrName = item.nutritionName,
                            kcal = item.kcal,
                            unit = item.unitName,
                            onClick = {}
                        ) {
                            Row() {
                                Image(
                                    modifier = Modifier.clickable {
                                        scope.launch {
                                            modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
                                        }
                                    },
                                    painter = painterResource(id = R.drawable.ic_red_delete),
                                    contentDescription = ""
                                )
                            }
                        }
                    }
                }
            }
        }
    }

导航:

...
  composable(
            route = "${ContentWidgetScreens.SelectedItemListScreen.route}/{$SELECTED_NTR_ITEM_LIST}",
            arguments = listOf(
                navArgument(SELECTED_NTR_ITEM_LIST) {
                    type = ANSAssetParamType()
                }
            )
        ) {backStackEntry ->

            val selectedNtrItemList = backStackEntry.arguments?.getParcelable<SelectedNtrItem>(SELECTED_NTR_ITEM_LIST)
            SelectedItemListRoute(navHostController,selectedNtrItemList)
        }
  composable(
            route = "${ContentWidgetScreens.AddNutritionSearchScreen.route}?$MEAL_ID={$MEAL_ID}",
            arguments = listOf(
                navArgument(name = MEAL_ID) {
                    type = NavType.IntType
                    defaultValue = -1
                })
          )
        { backStackEntry ->
            val mealId = backStackEntry.arguments?.getInt(MEAL_ID)
            AddNutritionSearchRoute(navHostController,mealId)
        }
...
Kotlin android-jetpack-compose android-viewmodel

评论

0赞 Abhimanyu 10/27/2023
根据需要将数据存储在较低层,如数据源、存储库等。然后,可以将其与不同的视图模型共享。
0赞 Tunahan 10/27/2023
您还可以在 Navigation 文件中创建 viewmodel 实例,而不是 SelectedItemListRoute 的参数,并将相同的 viewmodel 传递给两个可组合项。这样,您就可以将视图模型用作 sharedViewmodel。
0赞 NewPartizal 10/27/2023
我怎么能做到这一点?你能举个简单的例子吗
0赞 NewPartizal 10/27/2023
而且我还了解到移动视图模型不是正确的方法。我有其他方法可以做吗?更多更好
0赞 WBarber 10/28/2023
你不需要移动视图模型,本质上你需要“单一事实来源”。只需对两个目标使用一个视图模型。在将视图模型传递到屏幕的导航中,只需将同一视图模型的相同实例传递到两个屏幕即可。实质上,您将两个视图模型合二为一。您可以在屏幕之间传递数据,但不建议对复杂数据这样做,因为这可能会导致数据丢失。今晚晚些时候,当我有时间的时候,我可以给你举个例子,如果这没有意义的话。

答:

0赞 WBarber 10/28/2023 #1

因此,您可以在导航中执行此操作,例如NavHost。只需将 ViewModel 传递给 NavHost,然后从 NavHost 向下传递到您希望访问它的屏幕即可。这样,两个屏幕都可以访问同一个 ViewModel 实例

fun NavHostExample(
    navController: NavHostController,
    sharedViewModel: MySharedViewModel
) {
    NavHost(navController) {
        composable( route = Screens.MainMenuScreen.route ) {
            MainScreen(
                navController = navController,
                viewModel = sharedViewModel // passing the ViewModel to first screen
            )
        }

        composable( route = Screens.Settings.route ) {
            SettingsScreen(
                navController = navController,
                viewModel = sharedViewModel // passing same ViewModel instance to second screen
            )
        }
    }
}

只需初始化您的 ViewModel 并将其传递给您的 NavHost,无论您在哪里:

// Initialise
val sharedViewModel = MySharedViewModel()

// Pass to nav controller, and we have it setup to pass to views
NavHostExample(
    navController = navController,
    sharedViewModel = sharedViewModel
)

这样,我们就不会通过导航传递复杂的数据,而是指向 SharedViewModel 的指针,两个屏幕现在都可以访问该指针,使 SharedViewModel 成为我们的 DataLayer