I have a ViewModel handling my business logic and I am using Koin to inject this into my activity and each of my fragments. However after I navigate from Fragment A - Fragment B and navigate back to Fragment A, my observer is triggered again. Why is this happening and how do I stop this onChanged being triggered when I navigate back?
I have tried setting both 'this' and 'viewLifecycleOwner' as the LifecycleOwner of the LiveData.
I have also tried moving the observable to onCreate, onActivityCreated and onViewCreated
My ViewModel:
class MyViewModel : ViewModel() {
private val _myData = MutableLiveData<Data>()
val myData = LiveData<Data>()
get() = _myData
fun doSomething() {
... // some code
_myData.postValue(myResult)
}
MyActivity:
class Activity : BaseActivity() {
private val viewModel by viewModel<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
setSupportActionBar(main_toolbar)
subscribeUI()
}
private fun subscribeUI() {
myViewModel.isLoading.observe(this, Observer {
toggleProgress(it)
})
}
}
Fragment A:
class FragmentA : BaseFragment() {
private val viewModel by sharedViewModel<MyViewModel>()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
subscribeUI()
}
private fun subscribeUI() {
viewModel.myData.observe(viewLifecycleOwner, Observer {
val direction =
FragmentADirections.actionAtoB()
mainNavController?.navigate(direction)
})
}
}
Fragment B:
class FragmentB : BaseFragment() {
private val authViewModel by sharedViewModel<LoginViewModel>()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
subscribeUI()
}
private fun subscribeUI() {
viewModel.otherData.observe(viewLifecycleOwner, Observer {
// Do something else...
})
}
}
When I navigate from Fragment A -> Fragment B, everything works as I expect. However when I navigate back to Fragment A from Fragment B (by pressing the back button) the onChanged method from the Observer on myData is called and the navigation moves back to Navigation B.
This is an expected behaviour when using MutableLiveData
. I think your problem has nothing to do as to where to add or remove subscribers.
MutableLiveData
holds last value it is set with.
When we go back to previous fragment, our LiveData
observes are notified again with existing values. This is to preserve the state of your fragment and that's the exact purpose of LiveData
.
Google itself has addressed this and provided a way to override this behaviour, which is using an Event wrapper.
Let's see code in action
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let { value ->
onEventUnhandledContent(value)
}
}
}
// Declare live data object
val testLiveData: MutableLiveData<Event<Boolean>
by lazy{ MutableLiveData<Event<Boolean>>() }
testLiveData.postValue(Event(true))
viewModel?.testLiveData?.observe(this, EventObserver { result ->
// Your actions
}
Detailed reference: https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With