Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
431 views
in Technique[技术] by (71.8m points)

android - Why LiveData observer is being triggered twice for a newly attached observer

My understanding on LiveData is that, it will trigger observer on the current state change of data, and not a series of history state change of data.

Currently, I have a MainFragment, which perform Room write operation, to change non-trashed data, to trashed data.

I also another TrashFragment, which observes to trashed data.

Consider the following scenario.

  1. There are currently 0 trashed data.
  2. MainFragment is the current active fragment. TrashFragment is not created yet.
  3. MainFragment added 1 trashed data.
  4. Now, there are 1 trashed data
  5. We use navigation drawer, to replace MainFragment with TrashFragment.
  6. TrashFragment's observer will first receive onChanged, with 0 trashed data
  7. Again, TrashFragment's observer will secondly receive onChanged, with 1 trashed data

What is out of my expectation is that, item (6) shouldn't happen. TrashFragment should only receive latest trashed data, which is 1.

Here's my codes


TrashFragment.java

public class TrashFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ...

        noteViewModel.getTrashedNotesLiveData().removeObservers(this);
        noteViewModel.getTrashedNotesLiveData().observe(this, notesObserver);

MainFragment.java

public class MainFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ...

        noteViewModel.getNotesLiveData().removeObservers(this);
        noteViewModel.getNotesLiveData().observe(this, notesObserver);

NoteViewModel .java

public class NoteViewModel extends ViewModel {
    private final LiveData<List<Note>> notesLiveData;
    private final LiveData<List<Note>> trashedNotesLiveData;

    public LiveData<List<Note>> getNotesLiveData() {
        return notesLiveData;
    }

    public LiveData<List<Note>> getTrashedNotesLiveData() {
        return trashedNotesLiveData;
    }

    public NoteViewModel() {
        notesLiveData = NoteplusRoomDatabase.instance().noteDao().getNotes();
        trashedNotesLiveData = NoteplusRoomDatabase.instance().noteDao().getTrashedNotes();
    }
}

Code which deals with Room

public enum NoteRepository {
    INSTANCE;

    public LiveData<List<Note>> getTrashedNotes() {
        NoteDao noteDao = NoteplusRoomDatabase.instance().noteDao();
        return noteDao.getTrashedNotes();
    }

    public LiveData<List<Note>> getNotes() {
        NoteDao noteDao = NoteplusRoomDatabase.instance().noteDao();
        return noteDao.getNotes();
    }
}

@Dao
public abstract class NoteDao {
    @Transaction
    @Query("SELECT * FROM note where trashed = 0")
    public abstract LiveData<List<Note>> getNotes();

    @Transaction
    @Query("SELECT * FROM note where trashed = 1")
    public abstract LiveData<List<Note>> getTrashedNotes();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public abstract long insert(Note note);
}

@Database(
        entities = {Note.class},
        version = 1
)
public abstract class NoteplusRoomDatabase extends RoomDatabase {
    private volatile static NoteplusRoomDatabase INSTANCE;

    private static final String NAME = "noteplus";

    public abstract NoteDao noteDao();

    public static NoteplusRoomDatabase instance() {
        if (INSTANCE == null) {
            synchronized (NoteplusRoomDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(
                            NoteplusApplication.instance(),
                            NoteplusRoomDatabase.class,
                            NAME
                    ).build();
                }
            }
        }

        return INSTANCE;
    }
}

Any idea how I can prevent from receiving onChanged twice, for a same data?


Demo

I created a demo project to demonstrate this problem.

As you can see, after I perform write operation (Click on ADD TRASHED NOTE button) in MainFragment, when I switch to TrashFragment, I expect onChanged in TrashFragment will only be called once. However, it is being called twice.

enter image description here

Demo project can be downloaded from https://github.com/yccheok/live-data-problem

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I have introduced just one change in your code:

noteViewModel = ViewModelProviders.of(this).get(NoteViewModel.class);

instead of:

noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);

in Fragment's onCreate(Bundle) methods. And now it works seamlessly.

In your version you obtained a reference of NoteViewModel common to both Fragments (from Activity). ViewModel had Observer registered in previous Fragment, I think. Therefore LiveData kept reference to both Observer's (in MainFragment and TrashFragment) and called both values.

So I guess the conclusion might be, that you should obtain ViewModel from ViewModelProviders from:

  • Fragment in Fragment
  • Activity in Activity

Btw.

noteViewModel.getTrashedNotesLiveData().removeObservers(this);

is not necessary in Fragments, however I would advise putting it in onStop.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...