SNM
SNM

Reputation: 6795

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 ?

Upvotes: 3

Views: 3690

Answers (5)

fammi farendra
fammi farendra

Reputation: 17

it happened to me, my model observer firing again when backpressed, even there's no ajax request. My solution is i set flag isAjax=true whenever ajax request And in my model observer i put like this

Model.getResult().observe(getViewLifecycleOwner(), apiResult -> {
            hideProgress();
            if(!Model.isAjax){return;}
            Model.isAjax=false;
            if (apiResult == null) {

            }else if (apiResult.getError() != null) {

So observer progress will return immediately if there's no ajax request, or we can say observer didnot execute if we back pressed

Upvotes: 0

Tinashe Makuti
Tinashe Makuti

Reputation: 161

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.

Upvotes: 0

Rasoul Miri
Rasoul Miri

Reputation: 12222

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()

Upvotes: 6

Sushant Gosavi
Sushant Gosavi

Reputation: 3835

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

Upvotes: 4

alperozge
alperozge

Reputation: 136

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.

Upvotes: 0

Related Questions