LiveData observer's onChanged gets called on activity rotation even though there was no change in the data
Asked Answered
T

3

9

I'm working with ViewModels and LiveData in an Android app, and I want to use them to keep track of data for an Activity even when the screen is rotated. This works quite well, but there's one issue I can't fix. In the Activity's onCreate method, I register an observer for a LiveData containing a list of objects, which should just add a Fragment to the activity if the data is loaded. Then, I only reload the data if savedInstanceState is null, which should stop it from being reloaded in the event of a screen rotation. This looks like this:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        model = ViewModelProviders.of(this).get(MainActivityModel.class);
        observeList();
        if (savedInstanceState == null) {
            loadList(); //Reload the list and call postValue() on the LiveData.
        }
    }

    private void observeList() {
        model.getList().observe(this, new Observer<ArrayList<Object>>(){
            @Override
            public void onChanged(@Nullable ArrayList<Object> objects) {
                //Add list fragment whenever data changes.
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.container, ListFragment.getInstance(list))
                        .commit();
            }
        });
    }

The way I understand it, the ListFragment should only be shown when data changes. However, after some debugging, it seems the onChanged method in the observeList method gets called every single time the screen is rotated. Additionally, I checked whether the actual list was changed and it was exactly the same, no difference at all and there was never even a postValue() or setValue() method call. Therefore, I have no idea why the onChanged method would be called on screen rotation.

This also only happens when the list contains something. When the app is started up, the list has a length of 0 before loadList() is called. When the observer is registered at this state, the onChanged method is not called.

My best guess is that the onChanged method gets triggered either because the list is not empty when the observer is registered so the observer could think there is a change in data. Can anyone explain why this might be happening?

Teddy answered 8/2, 2019 at 12:49 Comment(1)
I have a similar problem. When screen rotation occurs, the last liveData state is called and the corresponding code is executed! For example, if it was ERROR state, the error will be displayed again (the number of screen rotations), and if it was SUCCESS state, the corresponding codes will be executed (in my project new data will be added repeatedly to the end of the recycler viewe)!Cancellation
S
7

This is working as intended. From livedata overview:

To ensure that the activity or fragment has data that it can display as soon as it becomes active. As soon as an app component is in the STARTED state, it receives the most recent value from the LiveData objects it’s observing. This only occurs if the LiveData object to be observed has been set.

Although there's missing code snippets - loadList should be a part of ViewModel itself, not a method of activity.

Sauerbraten answered 8/2, 2019 at 14:0 Comment(6)
Ah sorry I didn't see that, that makes sense then. About the loadList, it is basically just a wrapper for calling a method that is part of the ViewModel and a few other small things.Teddy
I still don't understand this well. So onChanged will still be called even though there is no change in data; but only when activity is created, I guess? ( such as after screen rotation). If that's correct, onChanged method is used to set the data, too. Even though it doesn't correspond to the purpose provided in the docs: "Called when the data is changed."Oscillation
@Oscillation that's the intent of MVVM - activity (or fragment) is only supposed to listen to changes in dataset that is exposed thru ViewModel. onChanged is not called to set the data, it provides current data that should be displayed in the view.Sauerbraten
.. it provides current data, but even when the data is not changed? Why?Oscillation
This does not make sense, If I wanted to init data on create, I would just get the data from viewmodel. What is the workaround? I only want observe to be called when data is changed.Thrips
It makes sense if you want to display something on the screen (like a detail screen), but it does cause issues if you are observing a database update so that some change can take place when a new id is created.Carpentaria
B
0

I had the same problem and I found a solution in this code lab: Training LiveData

Usually, LiveData delivers updates to the observers only when data changes. An exception to this behavior is that observers also receive updates when the observer changes from an inactive to an active state.

When the (...) fragment is re-created after a screen rotation, it moves from an inactive to an active state. The observer in the fragment is re-connected to the existing ViewModel and receives the current data.

So the solution is basically to create some method in the View Model that resets the value of the variable that triggers the observer (in your case, the list).

Blucher answered 29/10, 2021 at 9:53 Comment(0)
S
0

when a screen is rotated, the following callback methods in the activity lifecycle are called

  1. onSaveInstanceState()
  2. onPause()
  3. onStop()
  4. onCreate()
  5. onStart()
  6. onRestoreInstanceState()
  7. onResume()

it would help if you kept in mind that the live data observer is lifecycle aware, meaning that it will observe when the UI is in an active state and will stop observing when the UI is not in an active state. when the onStop method is called the live data observer stops listening and when the onCreate method is called again the live data observer starts to observe again. that is why the observer is reloaded .

Sheeree answered 8/8, 2022 at 15:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.