ViewModel onchange gets called multiple times when back from Fragment

33,477

Solution 1

The problem here is that when you dettach the fragment from the acitivity, both fragment and its viewmodel are not destroyed. When you come back, you add a new observer to the livedata when the old observer is still there in the same fragment (If you add the observer in onCreateView()). There is an article (Even a SO thread in fact) talking about it (with solution).

The easy way to fix it (also in the article) is that remove any observer from the livedata before you add observer to it.

Update: In the support lib v28, a new LifeCycleOwner called ViewLifeCycleOwner should fix that more info in here

Solution 2

Here is an example how i solve this problem .[TESTED AND WORKING]

 viewModel.getLoginResponse().observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String response) {
            if(getViewLifecycleOwner().getLifecycle().getCurrentState()== Lifecycle.State.RESUMED){
                // your code here ...
            }

        }
    });

Solution 3

here is what you are doing wrong...

  viewmModel.observeData().observe(getActivity(), new Observer<String>() {
    @Override
    public void onChanged(@Nullable String s) {

        if(s.equals("0")) {
            BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
            if (fragment == null) {
                fragment = BlankFragment.newInstance();
            }
            addFragmentToActivity(manager,
                    fragment,
                    R.id.root_activity_detail,
                    DETAIL_FRAG
            );
        } else {
            Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
        }
    }
});

in above code instead of "getActivity()" either you can use "this" or "viewLifecycleOwner".

Because as you are passing the getActivity() in observe method, whenever you open your fragment you are attaching the new instance of the observer with the Activity not with the fragment. So observer will keep alive even if you kill your fragment. So when livedata postvalue, it will send data to all the observers, as there are too many observers observing livedata, then all will get notified. Because of this, your observer gets called too many times. so you have to observe live data in fragment something like this.

  viewmModel.observeData().observe(this, new Observer<String>() {
    @Override
    public void onChanged(@Nullable String s) {

        if(s.equals("0")) {
            BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
            if (fragment == null) {
                fragment = BlankFragment.newInstance();
            }
            addFragmentToActivity(manager,
                    fragment,
                    R.id.root_activity_detail,
                    DETAIL_FRAG
            );
        } else {
            Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
        }
    }
});  

But still your onchanged method will get called two times.
You can stop this by checking one condition inside your onchanged method..

    dash_viewModel.getDashLiveData().observe(viewLifecycleOwner, object : Observer<AsyncResponse> {
        override fun onChanged(t: AsyncResponse?) {
            if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
                setData(t)
            }

        }

    })

from my research, I have found out that, if fragment using the ViewModel of its corresponding activity, So when even you start observing the livedata, it will first send you the most recently emitted item. even if you didn't call it from your fragment.

so onChange method got called two times

  1. When the fragment is on start state - to receive the most recently emitted item

  2. When the fragment is on Resumed state - to receive the call made by fragment either for api.

so on changed I always check the state of the fragment with the help of viewLifecycleOwner like this

   if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
      // if the fragment in resumed state then only start observing data
        }

viewlifecycleowner is provided by both Fragments and Activity as Google implemented this solution directly in support library 28.0.0 and androidx with getViewLifecycleOwner() method. viewlifecycleowner contains info about the lifecycle of the component.

in java you can use getViewLifecycleOwner() intead of viewlifecycleowner .

Solution 4

Instead of using getActivity as LifecycleOwner, you should use fragment.

Change

viewModel.observeData().observe(getActivity(), new Observer<String>() {

to

viewModel.observeData().removeObservers(this);
viewModel.observeData().observe(this, new Observer<String>() {

Solution 5

You shouldn't create your viewmModel in onCreateView but rather in onCreate so you don't add a listener to your data each time view is created.

Share:
33,477
Nikolas Bozic
Author by

Nikolas Bozic

Updated on October 26, 2021

Comments

  • Nikolas Bozic
    Nikolas Bozic over 2 years

    I am working with Android architecture components. What i want is when user type "0" in Edittext and click on Button to replace Fragment with new one , and if type anything else post Toast error message. In Problem is when i back from new Fragment(BlankFragment) and click on button again and type "0" again and click, onchange() is called multiple times so Fragment is get created multiple times

    FragmentExample.class:

         @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            manager = getActivity().getSupportFragmentManager();
            viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
                    .get(VModel.class);
    
            View v = inflater.inflate(R.layout.fragment_list, container, false);   
            b = (Button) v.findViewById(R.id.b);
            et = (EditText) v.findViewById(R.id.et);
    
            viewmModel.observeData().observe(getActivity(), new Observer<String>() {
                @Override
                public void onChanged(@Nullable String s) {
    
                    if(s.equals("0")) {
                        BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
                        if (fragment == null) {
                            fragment = BlankFragment.newInstance();
                        }
                        addFragmentToActivity(manager,
                                fragment,
                                R.id.root_activity_detail,
                                DETAIL_FRAG
                        );
                    } else {
                        Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
                    }
                }
            });
    
            b.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    viewmModel.setData(et.getText().toString());
                }
            });
            return v;
        }
        private void addFragmentToActivity(FragmentManager fragmentManager, BlankFragment fragment, int root_activity_detail, String detailFrag) {
            android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
            transaction.replace(root_activity_detail, fragment, detailFrag).addToBackStack(detailFrag);
            transaction.commit();
        }
    

    Repository class:

    
        public class Repository {
        MutableLiveData<String> dataLive = new MutableLiveData<>();  
    
        public Repository() {
    
        }
    
        public void setListData(String data) {
           dataLive.setValue(data);
        }
    
        public MutableLiveData<String> getData() {
            return dataLive;
        }
    }
    

    BlankFragment.class:

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
    
            listItemViewModel = ViewModelProviders.of(this, viewModelFactory)
                    .get(VModel.class);
            listItemViewModel.setData("");
            return inflater.inflate(R.layout.fragment_blank, container, false);
        }
    
  • wangqi060934
    wangqi060934 about 6 years
    I don't think so.The viewmodel created by ViewModelProviders is the same instance.
  • AJW
    AJW about 5 years
    I have a similar case. Can you provide an example to get me going in to right direction?
  • emirua
    emirua about 5 years
    just added a brief example
  • AJW
    AJW about 5 years
    Nice. I am using Java...do I assume the Java example would be similar? Basically use observe() on the Observer object in the ViewModel?
  • emirua
    emirua about 5 years
    You should try to use observe() in your Fragment, Activity or LifeCycleOwner for simplicity. If you do it in your ViewModel class you would have to use observeForever and remove this observer in onCleared() since you should never have a reference to your LifeCycleOwner in your ViewModel class. Hope that's clear.
  • AJW
    AJW about 5 years
    My bad, I meant to say use observe() that makes the Viewmodel observe the data again. In your example above what is the "this" in "viewModel.liveData.observe(this, dataObserver)"?
  • emirua
    emirua about 5 years
    in the example, "this" would be a reference to YourFragment
  • Choletski
    Choletski almost 5 years
    When we use observe(getActivity()) we share ViewModel from activity to fragments but if use only this it means that we register a new owner - Fragment and no more access to Activity LivewData
  • Rafael Ruiz Muñoz
    Rafael Ruiz Muñoz over 4 years
    This is probably the most efficient answer because there's no need to care or handle every single LiveData
  • Rafael Ruiz Muñoz
    Rafael Ruiz Muñoz about 4 years
    Down voters, happy for you to comment why this isn't working in order to improve it. Cheers!
  • djardon
    djardon about 4 years
    If use viewLifecycleOwner you can't create observers on onCreate method because you can't call viewLifecycleOwner before onCreateView() or after onDestroyView()
  • djardon
    djardon about 4 years
    Using viewLifecycleOwner have the same problem. When popBack to the fragment liveData called with last value emitted. You should put observer in onCreate and use this as owner.
  • Alvin Dizon
    Alvin Dizon almost 4 years
    @djardon this is true if you are using ViewBinding, as opposed to DataBinding.
  • Alberto M
    Alberto M almost 3 years
    I don't even want to know why this works, as long as it fixed it, thank you
  • Akarsh M
    Akarsh M almost 3 years
    kudos .............
  • Kishan Solanki
    Kishan Solanki over 2 years
    This idea is good but the problem is that I am having a fragment inside another fragment, like tab layout inside a fragment. So onViewCreated is called for inner fragment but onStop ondestroyview are not called for inner fragment.