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.
MainFragment
is the current active fragment. TrashFragment
is not created yet.MainFragment
added 1 trashed data.MainFragment
with TrashFragment
.TrashFragment
's observer will first receive onChanged
, with 0 trashed dataTrashFragment
'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
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);
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);
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();
}
}
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?
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.
Demo project can be downloaded from https://github.com/yccheok/live-data-problem
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
.