'My observer is always firing when I come back from one fragment to another
I'm using Navigation Components, I have Fragment A and Fragment B, from Fragment A I send an object to Fragment B with safe args and navigate to it.
override fun onSelectableItemClick(position:Int,product:Product) {
val action = StoreFragmentDirections.actionNavigationStoreToOptionsSelectFragment(product,position)
findNavController().navigate(action)
}
Now, after some logic in my Fragment B , I want to deliver that data to Fragment A again, which I use
btn_add_to_cart.setOnClickListener {button ->
findNavController().previousBackStackEntry?.savedStateHandle?.set("optionList",Pair(result,product_position))
findNavController().popBackStack()
}
Then in Fragment A, I catch up this data with
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<Pair<MutableList<ProductOptions>,Int>>("optionList")
?.observe(viewLifecycleOwner, Observer {
storeAdapter.updateProductOptions(it.second,it.first)
})
Now, this is working fine, but if I go from Fragment A to Fragment B and press the back button, the observer above fires again duplicating my current data, is there a way to just fire this observer when I only press the btn_add_to_cart
button from Fragment B ?
Solution 1:[1]
You use this extenstion:
fun <T> Fragment.getResult(key: String = "key") =
findNavController().currentBackStackEntry?.savedStateHandle?.get<T>(key)
fun <T> Fragment.getResultLiveData(key: String = "key"): MutableLiveData<T>? {
viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) {
findNavController().previousBackStackEntry?.savedStateHandle?.remove<T>(key)
}
})
return findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<T>(key)
}
fun <T> Fragment.setResult(key: String = "key", result: T) {
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, result)
}
Example:
FragmentA -> FragmentB
Fragment B need to set the result of the TestModel.class
ResultTestModel.class
data class ResultTestModel(val id:String?, val name:String?)
Fragment A:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// ...
getNavigationResultLiveData<PassengerFragmentResultNavigationModel>(
"UNIQUE_KEY")?.observe(viewLifecycleOwner) { result ->
Log.i("-->","${result.id} and ${result.name}")
}
//...
}
Fragment B: set data and call popBackStack.
ResultTestModel(id = "xyz", name = "Rasoul")
setNavigationResult(key = "UNIQUE_KEY", result = resultNavigation)
findNavController().popBackStack()
Solution 2:[2]
It is not clear from your code where your last piece of code is called - where you add an Observer to LiveData. I am guessing it is inside one of the methods onResume() or onViewStateRestored() or any other lifecycle callback which is called again whenever you return to Fragment A from Fragment B. If that is the case, then you are adding a new observer to the LiveData and any observer of a LiveData receives an instant update for the current value.
Move that piece of code to one of the callbacks methods which is called only once during the lifecycle of a fragment.
Solution 3:[3]
Facing same issue
Resolve this by removing old data from
savedStateHandle
live data
Inside Fragment B :
button?.setOnClickListener {
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, data)
findNavController().popBackStack()
}
Inside Fragment A:
Here is key to remove the old data by using live data remove method and it should be after view created like in onViewCreated method of fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>(key)?.observe(viewLifecycleOwner) {
result(it)
findNavController().currentBackStackEntry?.savedStateHandle?.remove<String>(key)
}
}
Update :
I have created Extension for this for better usage
fun <T> Fragment.setBackStackData(key: String, data: T, doBack: Boolean = false) {
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, data)
if (doBack)
findNavController().popBackStack()
}
fun <T> Fragment.getBackStackData(key: String, singleCall : Boolean= true , result: (T) -> (Unit)) {
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<T>(key)
?.observe(viewLifecycleOwner) {
result(it)
//if not removed then when click back without set data it will return previous data
if(singleCall) findNavController().currentBackStackEntry?.savedStateHandle?.remove<T>(key)
}
}
Calling inside fragment be like
While setting data in fragment B
var user : User = User(data) // Make sure this is parcelable or serializable
setBackStackData("key",user,true)
While getting data inside fragment A
getBackStackData<User>("key",true) { it ->
}
Thanks to This Guy
Solution 4:[4]
https://stackoverflow.com/a/66111168/8354145
This answer should help in this case. Use SingleLiveEvent.
Otherwise in these cases, maybe using a shared view model (might be scoped to the nav graph) however you won't need to use savedStateHandle
.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Rasoul Miri |
Solution 2 | alperozge |
Solution 3 | sushant gosavi |
Solution 4 | Tinashe Makuti |