Search code examples
androidmvvmviewmodelandroid-livedata

Is passing MutableLiveData to other ViewModels valid practice?


I have a scenario, where two fragments (A and B) need to share a list of objects, which can be mutated from both screens. A and B fragments have a corresponding ViewModel each (A_ViewModel and B_ViewModel). They contain some logic on how the shared data is mutated.

To keep the shared data in tact, I have created AB_Flow_ViewModel, which is activity scoped and contains that shared data wrapped in a MutableLiveData.

I have decided to pass that MutableLiveData object to A and B child fragments as a constructor parameter.

I have never seen such approach anywhere, but it seems to work perfectly for me.

Are there any drawbacks of doing this?

This is the pseudo code for my solution:

// Navigation: (A ->  B)
class A() : Fragment() {

  private val viewModel: A_ViewModel ...

  override fun onViewCreated(
      view: View,
      savedInstanceState: Bundle?
  ) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.sharedData.observe {
      ...
    }
  }

}


class A_ViewModel @Inject  constructor(
    val sharedData: MutableLiveData<List<Any>>
) : ViewModel() {

  fun changeSharedData(newSharedData: List<Any>) {
    sharedData.value = newSharedData
  }
}

@Module
class A_Module {

  @Provides
  @FragmentScoped
  fun provideSharedData(fragment: A) =
      fragment.activityViewModels<AB_Flow_ViewModel>().value.sharedData
}

class B() : Fragment() {

  private val viewModel: A_ViewModel ...

  override fun onViewCreated(
      view: View,
      savedInstanceState: Bundle?
  ) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.sharedData.observe {
      ...
    }
  }
}

@Module
class B_Module {

  @Provides
  @FragmentScoped
  fun provideSharedData(fragment: B) =
      fragment.activityViewModels<AB_Flow_ViewModel>().value.sharedData
}


class B_ViewModel @Inject constructor(
    val sharedData: MutableLiveData<List<Any>>
) : ViewModel() {
  fun changeSharedData(newSharedData: List<Any>) {
    sharedData.value = newSharedData
  }
}

class AB_Flow_ViewModel() : ViewModel() {
  val sharedData = MutableLiveData<List<Any>>()
}

Solution

  • I strongly suggest not to share MutableLiveData between two fragments, if you get a bug it will get super hard to debug because you won't know who is changing that data and where to cause this weird state. What I like to do is keep all my MutableLiveData inside the ViewModel and expose only LiveData to the Fragments/View to observe. Every change that needs to happen to the MutableLiveData I do it through a function inside the ViewModel.

    private val _sharedData = MutableLiveData<List<Any>>()
    val sharedData: LiveData<List<Any>>
        get() = _sharedData
    

    I would put fun changeSharedData inside AB_Flow_ViewModel and place all the logic in one ViewModel instead of two. If the logic needs to be different for FragA and FragB I would use two different methods that will change the same LiveDdata