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.>
I found out specifically why it's acting the way it is. The observed behavior was onChanged() in the trash fragment is called once the first time you activate the fragment after trashing a note (on fresh app start) and gets called twice when fragment get activated thereafter after a note is trashed.
The double calls happen because:
Call #1: The fragment is transitioning between STOPPED and STARTED in its lifecyle and this causes a notification to be set to the LiveData object (it's a lifecycle observer after all!). The LiveData code calls the the onChanged() handler because it thinks the observer's version of the data needs to be updated (more on this later). Note: the actual update to the data could still be pending at this point causing the onChange() to get called with stale data.
Call #2: Ensues as a result of the query setting the LiveData (normal path). Again the LiveData object thinks the observer's version of the data is stale.
Now why does onChanged() only get called once the very first time the view is activated after app startup? It's because the first time the LiveData version checking code executes as a result of the STOPPED->STARTED transition the live data has never been set to anything and thus LiveData skips informing the observer. Subsequent calls through this code path (see considerNotify() in LiveData.java) execute after the data has been set at least once.
LiveData determines if the observer has stale data by keeping a version number that indicates how many times the data has been set. It also records the version number last sent to the client. When new data is set LiveData can compare these versions to determine if an onChange() call is warranted.
Here's the version #s during the calls to the LiveData version checking code for the 4 calls:
Ver. Last Seen Ver. of the OnChanged()
by Observer LiveData Called?
-------------- --------------- -----------
1 -1 (never set) -1 (never set) N
2 -1 0 Y
3 -1 0 Y
4 0 1 Y
If you're wondering why version last seen by the observer in call 3 is -1 even though onChanged() was called the 2nd time around it's because the observer in calls 1/2 is a different observer than the one in calls 3/4 (the observer is in the fragment which was destroyed when the user went back to the main fragment).
An easy way to avoid confusion regarding the spurious calls that happen as a result of lifecycle transitions is to keep a flag in the fragment intialized to false that indicates if the fragment has been fully resumed. Set that flag to true in the onResume() handler then check to see if that flag is true in your onChanged() handler. That way you can be sure you're responding to events that happened becuase data was truly set.