Why Livedata Observer Is Being Triggered Twice for a Newly Attached Observer

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:

  1. Ensure that REPRODUCE_BUG is set to true in MainFragment
  2. Install the app
  3. Click on "add trashed note" button
  4. Switch to TrashFragment
  5. Note that there was just one notification form LiveData with correct value
  6. Switch to MainFragment
  7. Click on "add trashed note" button
  8. Switch to TrashFragment
  9. 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



Leave a reply



Submit