简体   繁体   中英

LiveData observer is being triggered multiple times using Navigation Component

Scenario: I have two fragments named FirstFragment and UnitFragment . I go from FirstFragment to UnitFragment to select a unit to come back to FirstFragmet using navController.popBackStack(); and send unit data to FirstFragment which is observing unit data.

This is my onViewCreated of FirstFragment :

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    if (viewModel == null) { // Lazy Initialization
        ApiService apiService = ApiServiceProvider.getInstance();
        AddNewWareViewModelFactory addNewWareViewModelFactory = new AddNewWareViewModelFactory(apiService);
        viewModel = new ViewModelProvider(this, addNewWareViewModelFactory).get(AddWareViewModel.class);
    }

    Log.i(TAG, "OnViewCreated -----> Called");
    viewModel.callNewWare(parentCode);
    viewModel.getNewWareResponse().observe(getViewLifecycleOwner(),
            resObject -> Log.i(TAG, "API Response LiveData Count -----> " + count++)); // Started From Zero

    NavHostFragment navHostFragment = (NavHostFragment) requireActivity()
            .getSupportFragmentManager()
            .findFragmentById(R.id.container);

    binding.button.setOnClickListener(v -> {
        if (navHostFragment != null) {
            NavController navController = navHostFragment.getNavController();
            navController.navigate(FirstFragmentDirections.actionFirstFragmentToUnitFragment());
        }
    });


    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 -> binding.tvUnit.setText(unit.getTitle()));
        }
    }
}

This is the LogCat result:

--- Go to FirstFragment for first time ---
I/FirstFragment: OnViewCreated -----> Called
I/FirstFragment: API Response LiveData Count -----> 0
--- Button clicked to go to UnitFragment to select a unit ---
I/UnitFragment: Selected Unit -----> Meter
--- Come back to FirstFragment ---
I/FirstFragment: OnViewCreated -----> Called
I/FirstFragment: API Response LiveData Count -----> 1
I/FirstFragment: API Response LiveData Count -----> 2

As you see in LogCat result, every time I click the button and go to UnitFragment and come back to FirstFragment the onViewCreated will call again and API LiveDataObserver will be triggered TWO times!!!

I know onViewCreated will call again because Navigation Component replaces the fragments instead of adding them. but I don't know why LiveData observer is being triggered two times.

I read this post but he seems to have ignored Navigation Component.

I need a solution to...

  1. Avoid calling onViewCreated codes again.
  2. Avoid triggering LiveData observer again.

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());
        });
    }
}

1. Avoid calling onViewCreated codes again

No, I think you can't avoid that, because your FirstFragment's view destroy when you navigate to other. So it will be call view created again after you come back.

2. Avoid triggering LiveData observer again.

You can custom a live data like this:

class SingleLiveEvent<T> : MutableLiveData<T>() {

  private val pending: AtomicBoolean = AtomicBoolean(false)

  override fun setValue(value: T) {
    pending.set(true)
    super.setValue(value)
  }

  override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
    super.observe(
      owner,
      Observer { value ->
        if (pending.compareAndSet(true, false)) {
          observer.onChanged(value)
        }
      }
    )
  }
}

It's just notify to observer when you set a new data.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM