Reputation: 2779
I am developing an android app using Jetpack library:
Actually, I am familiar with MVP pattern.
I am trying to study MVVP pattern (Databinding and Jetpack ViewModel)
I have 2 fragments (A and B).
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
@AndroidEntryPoint
class AFragment {
private val viewModel: AViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel
with(binding) {
button.setOnClickListener {
[email protected]()
}
}
viewModel.result.observe(viewLifecycleOwner) { result ->
findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
}
}
}
And here is AViewModel:
@HiltViewModel
class AViewModel @Inject constructor(): ViewModel() {
private val _result: MutableLiveData<Int> = MutableLiveData()
val result: LiveData<Int>
get() = _result
fun doAction() {
_result.postValue(SOME_ACTION_RESULT)
}
}
It shows BFragment correctly.
But If I touch Back Button
on BFragment, it still shows BFragment.
Actually, It went to back AFragment, but it comes again to BFragment.
When I touch Back Button
on BFragment,
viewModel.result.observe(viewLifecycleOwner) { result ->
findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
}
Why this code is called again?
And do I write code correctly?
What is the best practice?
Now, I found a solution.
In AFragment:
viewModel.result.observe(viewLifecycleOwner) { result ->
if (result != null) {
findNavController().navigate(AFragmentDirections.actionAFragmentToBFragment(result))
viewModel.resetResult()
}
}
and In AViewModel:
fun resetResult() {
_result.postValue(null)
}
With this code, it works fine.
Yes... But I don't like this code...
It's... so weird...
I don't know what is the best practice...
Upvotes: 1
Views: 152
Reputation: 376
the problem is related with livedata and fragment lifecycle.
AFragment and AViewModel lives when you move to FragmentB, but view in AFragment detach and attach when you move and come back. It means onViewCreated() called every time when you touch Back button on BFragment. As a result, AFragment start to observe AViewModel which has already valid data with its _result livedata.
You should separate uidata and events in livedatas. Easiest solution is SingleEventLiveData implementation and use it.
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
}
}
in viewmodel:
private val _result: MutableLiveData<Event<Int>> = MutableLiveData()
val result: LiveData<Event<Int>>
get() = _result
fun doAction() {
_result.postValue(Event(5))
}
how to observe:
viewModel.result.observe(viewLifecycleOwner) { result ->
result.getContentIfNotHandled()?.let {
findNavController().navigate(R.id.action_fragment_a_to_fragment_b)
}
}
How to create LiveData which emits a single event and notifies only last subscribed observer?
Upvotes: 1