Solution 1 :
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
Problem :
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>>()
}
Comments
Comment posted by Jokubas Trinkunas
Thank you for your answer! I am aware, that this is Google proposed solution, but if I do it in this way, the trade off is that my parent ViewModel will become huge and this can be avoided if I do it in a way as described. I agree, with your point, that it might be harder to debug. Also, MutableLiveData is only exposed to A and B ViewModels, but not to the views.
Comment posted by Viktor Petrovski
You can organize your ViewModels per feature they don’t necessarily need to hold all the logic from FragA & FragB. Please mark this answer as accepted if it’s helpful so it can help others too. If you have some more questions please reach out to me I would love to help