【问题标题】:LiveData observer is being triggered multiple times using Navigation Component使用导航组件多次触发 LiveData 观察者
【发布时间】:2021-07-01 02:09:07
【问题描述】:

场景:我有两个名为 FirstFragmentUnitFragment 的片段。我从FirstFragmentUnitFragment 使用navController.popBackStack(); 选择一个返回到FirstFragmet 的单元,并将单元数据发送到FirstFragment,这是观察单元数据。

这是我的onViewCreatedFirstFragment

@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 观察者会被触发两次。

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

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

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

【问题讨论】:

  • 您能否记录下观察者返回的值resValue 并分享这些结果(即您何时看到它变为“Meter”)?此外,在更新UnitFragment 中的LiveData 对象时,您使用的是set 还是post 语义?
  • 我已经模拟了您的代码中缺少的部分,它对我来说可以正常工作。没有重复调用观察者。确保您的 API 按预期工作,并且您的实时数据对象仅在收到响应时更改一次。如果不是这种情况,请提供更多详细信息以获得更好的帮助。

标签: java android android-livedata android-architecture-navigation android-mvvm


【解决方案1】:

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

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

避免再次调用 onViewCreated 代码。

避免再次触发 LiveData 观察者。

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

我知道 onViewCreated 会再次调用,因为导航组件会替换片段而不是添加片段。

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

当你从后台弹出 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());
        });
    }
}

【讨论】:

  • 感谢您的有用回答以及有关删除 savedStateHandle 的建议。我认为最好将viewModel.getNewWareResponse().observe(...)onCreate 中删除,然后按照您所说的将其放入onViewCreated。给我的同胞投票让我开心;)
  • 我们需要将 Activity (requireActivity()) 而不是 Fragment (this) 传递给 ViewModelProvider,以便能够在 Activity 的 Fragment 之间共享 ViewModel 数据。我说的对吗?
【解决方案2】:

1. Avoid calling onViewCreated codes again

不,我认为您无法避免这种情况,因为当您导航到其他视图时,您的 FirstFragment 视图会破坏。所以回来后会再次创建调用视图。

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

它只是在您设置新数据时通知观察者。

【讨论】:

  • 它在这种情况下有效,但并非总是如此。假设根据FirstFragment中的API响应需要添加动态视图,我从UnitFragment返回FirstFragment时不会添加动态视图
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多