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

410 Views Asked by At

I have a list like this

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

I need to use this list on two composable screens, so I need to access a single example of it. My idea is to create it with singelton and use it on the UI and Viewmodel side, but I could not do this completely.

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

This is the class I created as singelton. How can I use this in the viewmodel and UI side of two composable screens, that is, how can I access this list on both the UI side and the viewmodel side on two screens as a single object because I need the same list?

@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() { ....

SOLVED

I solved the problem like below:

object SelectedNtrItemManager {

    var selectedItems =  SnapshotStateList<NutritionSearchItem>()

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

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

FirstScreen VM:

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

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

SecondScreen VM

class SecondScreenVM : ViewModel() {

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

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

SecondScreen UI:

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

To detect the changes I made I used SnapshotStateList. SnapshotStateList detects when there is a change and notifies listeners and I updated my SelectedNtrItemManager like above and it worked perfectly.

2

There are 2 best solutions below

4
On

I would put the data object into some kind of Repository class that gets injected into both of your ViewModels. There you can collect it as a flow and then collect it in your composable.

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()
}
1
On

You need to create the ViewModel as follows and pass it as a parameter to the relevant screens.

private val sharedViewModel by viewModels<SharedViewModel>()

@Composable
fun FirstScreen(
    viewModel: SharedViewModel
) {
}

@Composable
fun SecondScreen(
    viewModel: SharedViewModel
) {
}