【问题标题】:How to stop LiveData observer from firing automatically in Android MVVM architecture?如何阻止 LiveData 观察者在 Android MVVM 架构中自动触发?
【发布时间】:2020-04-03 20:48:12
【问题描述】:

考虑以下场景。我有两个片段,FormFragmentSelectionFragmentMainActivity 托管。我有一个SharedViewModel,我计划将其用于片段之间的数据通信。

FormFragment 有一个微调器,点击它会打开SelectionFragment

SelectionFragment 包含一个 RecyclerView,其中包含一个 Item 对象列表。

SharedViewModel 有两个 LiveData,LiveData<Item> selectedItemLiveData<List<Item>> items

用例:

  1. 在应用启动时,用户登陆FormFragment,它有一个没有选定项目的微调器
  2. 当用户点击微调器时,我们导航到SelectionFragment
  3. SelectionFragmentSharedViewModel 获取项目列表并填充 RecyclerView
  4. 当用户点击一个项目时,我们更新SharedViewModel中的selectedItem并从后面的堆栈中弹出SelectionFragment以返回到FormFragment
  5. FormFragment spinner 现在显示所选项目
  6. 用户可以通过执行相同的过程来选择不同的项目

问题:

在完成上面的步骤 1 到 5 之后,当我们尝试通过再次执行步骤 1 来选择另一个项目时,SelectionFragment 内部的观察者立即触发(因为selectedItem 不是null),并从回栈。这显然不是我们想要的行为,因为我们希望用户能够随意选择不同的项目。

如何正确观察SelectionFragment 内部的selectedItem,使其不会立即触发其观察者?

class FormFragment extends Fragment {

    @BindView(R.id.spinner) protected Spinner spinner;

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        SharedViewModel viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel.class);

        viewModel.getSelectedItem().observe(getViewLifecycleOwner(), selectedItem -> {
            spinner.setSelected(selectedItem);
        });

        spinner.setOnClickListener(v -> {
            // navigate to SelectionFragment
            requireFragmentManager().beginTransaction().replace(R.id.container, new SelectionFragment()).commit();
        });
    }
}

class SelectionFragment extends Fragment {

    @BindView(R.id.recycler_view) protected RecyclerView recyclerView;

    private ItemsAdapter adapter = new ItemsAdapter();

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        SharedViewModel viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel.class);

        viewModel.getItems().observe(getViewLifecycleOwner(), items -> {
            // load items into recycler view
            adapter.submitList(new ArrayList<>(items));
        });

        viewModel.getSelectedItem().observe(getViewLifecycleOwner(), selectedItem -> {
            // pop fragment from back stack
            requireFragmentManager().popBackStackImmediate();
        });

        adapter.setOnItemClickedListener(item -> {
            // update selected item in view model when user taps on an item in the list
            viewModel.setSelectedItem(item);
        });

        recyclerView.setAdapter(adapter);
    }
}

class SharedViewModel extends ViewModel {

    private final MutableLiveData<Item> selectedItem = new MutableLiveData<>();
    private Repository repository;

    // repository is injected (unimportant to the problem)
    SharedViewModel(Repository repository) {
        this.repository = repository;
    }

    LiveData<List<Item>> getItems() {
        return repository.getItems();
    }

    LiveData<Item> getSelectedItem() {
        return selectedItem;
    }

    void setSelectedItem(Item item) {
        selectedItem.setValue(item);
    }
}

【问题讨论】:

    标签: android android-fragments mvvm android-livedata android-viewmodel


    【解决方案1】:

    在您的FormFragment 中,从SelectionFragment 读取数据后,在LIveData 对象中将其设置为null

    或者,您可以将对象包装在 Consumable 中,类似这样

    class ConsumableValue<T>(private val data: T) {
    
        private var consumed = false
    
        @UiThread
        fun consume(block: ConsumableValue<T>.(T) -> Unit) {
            val wasConsumed = consumed
            consumed = true
            if (!wasConsumed) {
                this.block(data)
            }
        }
    
        @UiThread
        fun ConsumableValue<T>.release() {
            consumed = false
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-06
      • 1970-01-01
      • 2019-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多