使用导航组件多次触发 LiveData 观察器

LiveData observer is being triggered multiple times using Navigation Component

场景:我有两个名为 FirstFragmentUnitFragment 的片段。我从 FirstFragmentUnitFragment 再到 select 使用 navController.popBackStack(); 返回到 FirstFragmet 的单位并将单位数据发送到正在观察的 FirstFragment单位数据.

这是我的 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()));
        }
    }
}

这是 LogCat 结果:

--- 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

正如您在 LogCat 结果中看到的那样,每次我单击按钮并转到 UnitFragment 并返回 FirstFragment 时,onViewCreated 将再次调用并且 API LiveDataObserver 将被触发两次!!!

我知道 onViewCreated 会再次调用,因为导航组件会替换片段而不是添加片段。但我不知道为什么 LiveData 观察器会被触发两次。

我读了 但他似乎忽略了导航组件。

我需要一个解决方案...

  1. 避免再次调用 onViewCreated 代码。
  2. 避免再次触发 LiveData 观察器。

很遗憾,这不是您问题的答案:

I need a solution to ...

Avoid calling onViewCreated codes again.

Avoid triggering LiveData observer again.

我正在尝试解释导航及其行为或纠正一些误解。这些问题有不同的原因,Avoid calling onViewCreated codes again.是一种迂回的方式。

I know onViewCreated will call again because Navigation Component replaces the fragments instead of adding them.

如您所知,在将片段添加到后台堆栈时替换片段,只需将旧片段从 fragmentManager 中分离出来。这意味着旧片段的 view 将被破坏。

并且会在您从后台堆栈中弹出 UnitFragment 时创建其视图。

所以不要在 onViewCreated 中调用任何 API 调用,因为它可能会调用多次(在配置更改、破坏片段等...)

最好使用 onCreate 来初始化非视图相关的组件(ViewModel + API 调用)。它减少了 Lazy(!?) Initialization 检查。

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

并在onViewCreated开始观察他们。此外,您应该在获得 navBackStackEntry 后使用 unit_data

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

不,我认为您无法避免这种情况,因为当您导航到其他片段时,您的 FirstFragment 的视图会被破坏。所以回来后会重新创建call view

2. Avoid triggering LiveData observer again.

您可以像这样自定义实时数据:

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

设置新数据时通知观察者