簡體   English   中英

使用導航組件多次觸發 LiveData 觀察者

[英]LiveData observer is being triggered multiple times using Navigation Component

場景:我有兩個名為FirstFragmentUnitFragment的片段。 我 go 從FirstFragmentUnitFragment到 select 一個單元返回到FirstFragmet使用navController.popBackStack(); 並將單元數據發送到正在觀察單元數據的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 結果中看到的那樣,每次我單擊按鈕和 go 到UnitFragment並返回到FirstFragment時, onViewCreated將再次調用,API LiveDataObserver 將被觸發兩次!!!

我知道 onViewCreated 會再次調用,因為導航組件替換了片段而不是添加它們。 但我不知道為什么 LiveData 觀察者會被觸發兩次。

我讀了這篇文章,但他似乎忽略了導航組件。

我需要一個解決方案...

  1. 避免再次調用onViewCreated代碼。
  2. 避免再次觸發 LiveData 觀察者。

不幸的是,這不是您問題的答案:

我需要一個解決方案...

避免再次調用 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中觀察它們。 此外,您應該在獲得時使用來自unit_data的 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 視圖會破壞。 因此,您回來后將再次創建調用視圖。

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

它只是在您設置新數據時通知觀察者。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM