如何在 kotlin jetpack compose 中创建单例对象并用于两个屏幕的 UI 和视图模型?

How to create singleton object and using for UI and viewmodel for two screen in kotlin jetpack compose?

提问人:NewPartizal 提问时间:10/27/2023 最后编辑:NewPartizal 更新时间:11/1/2023 访问量:81

问:

我有这样的清单

 data class SelectedNtrItem(
        val items:ArrayList<NutritionSearchItem> = arrayListOf()
    )

我需要在两个可组合的屏幕上使用此列表,因此我需要访问其中的单个示例。我的想法是用 singelton 创建它并在 UI 和 Viewmodel 端使用它,但我无法完全做到这一点。

class SelectedNtrItemManager {
    companion object{
        private val _selectedNtrItem = MutableStateFlow(SelectedNtrItem())
        val selectedNtrItem: StateFlow<SelectedNtrItem> = _selectedNtrItem.asStateFlow()
    }
}

这是我创建的 singelton 类。如何在两个可组合屏幕的视图模型和UI端使用它,也就是说,我如何在两个屏幕上的UI端和视图模型端作为单个对象访问此列表,因为我需要相同的列表?

@Composable
fun FirstScreen(
    navHostController: NavHostController,
    viewModel: FirstScreenViewModel = hiltViewModel()
) {
...


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

@Composable
fun SecondScreen(
    navHostController: NavHostController,
    viewModel: SecondScreenViewModel = hiltViewModel()
) {
...


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

解决

我解决了下面的问题:

object SelectedNtrItemManager {

    var selectedItems =  SnapshotStateList<NutritionSearchItem>()

    fun addItem(item: NutritionSearchItem) {
        selectedItems.add(item)
    }

    fun removeItem(item: NutritionSearchItem) {
        selectedItems.remove(item)
    }
}

FirstScreen 虚拟机:

 val selectedItems: SnapshotStateList<NutritionSearchItem>
        get() = SelectedNtrItemManager.selectedItems

  fun addSelectedNutritionItem(
       ntrItem: NutritionSearchItem
   ) {
       if (selectedItems.contains(ntrItem)) {
           SelectedNtrItemManager.removeItem(ntrItem)
       } else {
           SelectedNtrItemManager.addItem(ntrItem)
       }
   }

SecondScreen 虚拟机

class SecondScreenVM : ViewModel() {

    val list: SnapshotStateList<NutritionSearchItem>
        get() = SelectedNtrItemManager.selectedItems

    fun deleteItemFromList(item: NutritionSearchItem) {
        SelectedNtrItemManager.removeItem(item)
    }
}

SecondScreen 用户界面:

  val list = viewModel.list
  LazyColumn(modifier = Modifier.padding(15.dp)) {
                items(list) { item ->....

为了检测我所做的更改,我使用了 SnapshotStateList。SnapshotStateList 检测何时发生更改并通知听众,我像上面一样更新了我的 SelectedNtrItemManager,它运行良好。

kotlin 依赖注入 android-jetpack-compose 单例 android-mvvm

评论

0赞 WBarber 10/28/2023
对两个屏幕使用相同的视图模型是否可行?如果两者不是太拥挤,则可以将它们组合在一起,并使用视图模型的单个实例,以便它们都可以访问相同的变量。这可能不是最好的选择,但我在设置屏幕上做到了这一点,因为我只有 5 个选项,我需要在主屏幕中访问每个选项。

答:

1赞 Faruk Karaca 10/28/2023 #1

您需要按如下方式创建 ViewModel,并将其作为参数传递到相关屏幕。

private val sharedViewModel by viewModels<SharedViewModel>()

@Composable
fun FirstScreen(
    viewModel: SharedViewModel
) {
}

@Composable
fun SecondScreen(
    viewModel: SharedViewModel
) {
}
1赞 froots 10/30/2023 #2

我会将数据对象放入某种 Repository 类中,该类会注入到您的两个 ViewModel 中。在那里,您可以将其收集为流,然后将其收集到可组合项中。

class Repository {

    val itemListFlow = MutableSharedFlow<SelectedNtrItem>()

}

class FirstScreenViewModel(repository: Repository): ViewModel() {

    val itemListFlow = repository.itemListFlow.stateIn(viewModelScope, SharingStarted.Eagerly, null)

}

@Composable
fun FirstScreen(
    navHostController: NavHostController,
    viewModel: FirstScreenViewModel = hiltViewModel()
) {
    val list by viewModel.itemListFlow.collectAsState()
}

class SecondScreenViewModel(repository: Repository): ViewModel() {

    val itemListFlow = repository.itemListFlow.stateIn(viewModelScope, SharingStarted.Eagerly, null)

}

@Composable
fun SecondScreen(
    navHostController: NavHostController,
    viewModel: FirstScreenViewModel = hiltViewModel()
) {
    val list by viewModel.itemListFlow.collectAsState()
}

评论

0赞 NewPartizal 10/31/2023
我怎样才能将其用于第二个屏幕?您可以通过添加第二个屏幕来执行您要做的事情吗?
0赞 froots 10/31/2023
我编辑了我的答案,只是做与你的第一个视图模型和可组合模型完全相同的事情
0赞 NewPartizal 10/31/2023
这是行不通的。
0赞 froots 11/2/2023
我忘记了您需要在 Hilt 中将存储库声明为单例。我不是刀柄专家,但我认为它只是使用@Singleton注释。