Как остановить автоматический запуск обозревателя LiveData в архитектуре Android MVVM? - PullRequest
0 голосов
/ 03 апреля 2020

Рассмотрим следующий сценарий. У меня есть два фрагмента, FormFragment и SelectionFragment, которые размещены на MainActivity. У меня есть SharedViewModel, который я планирую использовать для передачи данных между фрагментами.

FormFragment имеет счетчик, нажатие на которое открывает SelectionFragment.

SelectionFragment состоит из RecyclerView, который имеет список Item объектов.

SharedViewModel имеет две LiveDatas, LiveData<Item> selectedItem и LiveData<List<Item>> items

Вариант использования:

  1. При запуске приложения пользователь приземляется на FormFragment, у которого есть счетчик без выбранного элемента
  2. Когда пользователь нажимает на счетчик, мы переходим к SelectionFragment
  3. SelectionFragment получает список элементов из SharedViewModel и заполняет RecyclerView
  4. Когда пользователь нажимает на элемент, мы обновляем selectedItem в SharedViewModel и выталкиваем SelectionFragment из заднего стека в go обратно к FormFragment
  5. FormFragment теперь отображается выбранный элемент
  6. Пользователь может выбрать другой элемент, выполнив тот же процесс

Проблема:

После выполнения шагов с 1 по 5 выше, когда мы пытаемся выбрать другой элемент, выполнив Шаг 1 снова, наблюдатель указал e SelectionFragment немедленно запускается (поскольку selectedItem не null) и извлекает фрагмент из заднего стека. Это явно не желаемое поведение, так как мы хотим, чтобы пользователь мог выбирать другой элемент по желанию.

Как правильно наблюдать за selectedItem внутри SelectionFragment, чтобы он не вызывал его наблюдателя стрелять сразу?

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

1 Ответ

0 голосов
/ 03 апреля 2020

В вашем FormFragment после чтения данных из SelectionFragment установите для него значение null в LIveData объекте.

В качестве альтернативы вы можете обернуть свои объекты в 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
    }
}
...