Why LiveData observer is being triggered twice for a newly attached observer
I forked your project and tested it a bit. From all I can tell you discovered a serious bug.
To make the reproduction and the investigation easier, I edited your project a bit. You can find updated project here: https://github.com/techyourchance/live-data-problem . I also opened a pull request back to your repo.
To make sure that this doesn't go unnoticed, I also opened an issue in Google's issue tracker:
Steps to reproduce:
- Ensure that REPRODUCE_BUG is set to true in MainFragment
- Install the app
- Click on "add trashed note" button
- Switch to TrashFragment
- Note that there was just one notification form LiveData with correct value
- Switch to MainFragment
- Click on "add trashed note" button
- Switch to TrashFragment
- Note that there were two notifications from LiveData, the first one with incorrect value
Note that if you set REPRODUCE_BUG to false then the bug doesn't
reproduce. It demonstrates that subscription to LiveData in
MainFragment changed the behavior in TrashFragment.Expected result: Just one notification with correct value in any case.
No change in behavior due to previous subscriptions.More info: I looked at the sources a bit, and it looks like
notifications being triggered due to both LiveData activation and new
Observer subscription. Might be related to the way ComputableLiveData
offloads onActive() computation to Executor.
LiveData observer is being triggered multiple times using Navigation Component
Unfortunately, this is not the answer to your problems:
I need a solution to ...
Avoid calling onViewCreated codes again.
Avoid triggering LiveData observer again.
I'm trying to explain about navigation and its behaviors or correct some misunderstandings. These issues have different reasons and Avoid calling onViewCreated codes again.
is a devious way.
I know onViewCreated will call again because Navigation Component replaces the fragments instead of adding them.
As you know, replacing fragments when they were added in back-stack, just detaching old fragment from fragmentManager
. It means the old fragment's view will destroy.
And will create its view when you pop UnitFragment from the back-stack.
So don't call any API call in onViewCreated
because it may call multiple times (in configuration changes, destroying fragment, etc...)
It's better to use onCreate
for initializing non-view-related components (ViewModel + API calls). It reduces Lazy(!?) Initialization
check.
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ApiService apiService = ApiServiceProvider.getInstance();
AddNewWareViewModelFactory addNewWareViewModelFactory = new AddNewWareViewModelFactory(apiService);
viewModel = new ViewModelProvider(this /*owner*/, addNewWareViewModelFactory).get(AddWareViewModel.class);
viewModel.callNewWare(parentCode);
}
And start to observe them in onViewCreated
. Also, you should consume unit_data
from navBackStackEntry when you got it.
if (navHostFragment != null) {
NavController navController = navHostFragment.getNavController();
NavBackStackEntry navBackStackEntry = navController.getCurrentBackStackEntry();
if (navBackStackEntry != null) {
SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
MutableLiveData<Unit> unitLiveData = savedStateHandle.getLiveData("unit_data");
unitLiveData.observe(getViewLifecycleOwner(), unit -> {
savedStateHandle.remove("unit_data"); // add this line
return binding.tvUnit.setText(unit.getTitle());
});
}
}
Live data observer triggered twice on fragment created
I came across a handy class SingleLiveEvent
that we can use instead of LiveData
in ViewModel
class to send only new updates after subscription.
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, Observer<T> { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
fun call() {
postValue(null)
}
}
This LiveData
extension only calls the observable if there's an explicit call to setValue()
or call()
.
Update, primary constructor with parameter:
class SingleLiveEvent<T>(value: T) : MutableLiveData<T>(value) {...}
LiveData observer fired twice, even with viewLifecycleOwner
This happens because view model retains value when you open another fragment, but the fragment's view is destroyed. When you get back to the fragment, view is recreated and you subscribe to retailLiveData
, which still holds the previous value and notifies your observer as soon as fragment moves to started state. But you are calling retailViewModel.getRetailById(retail.id)
in onViewCreated
, so after awhile the value is updated and observer is notified again.
One possible solution is to call getRetailById()
from view model's init
method, the result will be cached for view model lifetime then.
Related Topics
App Crashes (Sometimes) With Fatal Signal 11 (Sigsegv), Code 1
Open External Links in the Browser With Android Webview
How to Quit Android Application Programmatically
How to Convert Image to Zpl Code for Printing Using Zebra Printer
How to Show the Number Keyboard on an Edittext in Android
How to Restrict Entry of Numbers But Allow All the Special Characters and Alphabets in Edittext
Passing an Arraylist of Objects to the New Activity
How to Make Image Fill Relativelayout Background Without Stretching
Html File Input in Android Webview (Android 4.4, Kitkat)
Android Prevent Immediate Trigger of Alarm Service If Alarm Time Has Passed For the Day
How to Check If a Value Exists Already in a Firebase Data Class Android
Android: Textview: Remove Spacing and Padding on Top and Bottom
Limitations on Opening Pdf File in Android
Android Studio - Textview Not Showing in Design View Layout
How to Dynamically Hide a Menu Item in Bottomnavigationview
How to Create a Video from an Array of Images in Android