Заливка адаптера из разных источников LiveData

Я играю с LiveData и хочу понять, на что он способен. Я хочу заполнить свой RecyclerView данными из разных источников с помощью переключателя (используя фильтры, если хотите).

Фильтрация значений внутри адаптера невозможна. Итак, я решил использовать МедиаторLiveData внутри моей модели представления.

Дао:

@Query("SELECT * FROM tasks WHERE completed = 0")
LiveData<List<Task>> getActiveTasksLiveData();

@Query("SELECT * FROM tasks")
LiveData<List<Task>> getAllTasksLiveData();

@Query("SELECT * FROM tasks WHERE completed = 1")
LiveData<List<Task>> getClosedTasksLiveData();

Репо:

    public LiveData<List<Task>> getActiveTasks() {
        return mTaskDao.getActiveTasksLiveData();
    }

    public LiveData<List<Task>> getAllTasks() {
        return mTaskDao.getAllTasksLiveData();
    }

    public LiveData<List<Task>> getClosedTasks() {
        return mTaskDao.getClosedTasksLiveData();
    }

ViewModel

public class MainViewModel extends AndroidViewModel {

    private final String TAG = "MainViewModel";

    private final AppDataRepository mData;

    private MediatorLiveData<List<Task>> mMediatorTasks;

    public MainViewModel(@NonNull Application application) {
        super(application);

        mData = AppDataInjector.getDataRepository(application.getApplicationContext());

        mMediatorTasks = new MediatorLiveData<>();
        mMediatorTasks.setValue(null);
    }

    public LiveData<List<Task>> getTasks(){
        return mMediatorTasks;
    }

    public void changeTasksOption(int index){
        mMediatorTasks.removeSource(mData.getAllTasks());
        mMediatorTasks.removeSource(mData.getActiveTasks());
        mMediatorTasks.removeSource(mData.getClosedTasks());
        if (index == R.id.navigation_all){
            Log.i(TAG, "Add source: all");
            mMediatorTasks.addSource(mData.getAllTasks(), new Observer<List<Task>>() {
                @Override
                public void onChanged(List<Task> tasks) {
                    Log.i(TAG, "Add source: all - setValue");
                    mMediatorTasks.setValue(tasks);
                }
            });
        } else if (index == R.id.navigation_closed){
            Log.i(TAG, "Add source closed");
            mMediatorTasks.addSource(mData.getClosedTasks(), new Observer<List<Task>>() {
                @Override
                public void onChanged(List<Task> tasks) {
                    Log.i(TAG, "Add source: closed - setValue");
                    mMediatorTasks.setValue(tasks);
                }
            });
        } else {
            Log.i(TAG, "Add source active");
            mMediatorTasks.addSource(mData.getActiveTasks(), new Observer<List<Task>>() {
                @Override
                public void onChanged(List<Task> tasks) {
                    Log.i(TAG, "Add source: active - setValue");

                    mMediatorTasks.setValue(tasks);
                }
            });
        }
    }
}

Фрагмент

    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);

        mNavigationView = view.findViewById(R.id.navigation);
        mFab = view.findViewById(R.id.fabMain);
        mRecyclerView = view.findViewById(R.id.mainRecyclerView);

        tasksAdapterLive = new TasksAdapterLive(mAdapterCallback);
        RecyclerView.LayoutManager manager = new GridLayoutManager(getContext(), 1);
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(tasksAdapterLive);

        // set up bottom navigation listener
        mNavigationView.setOnNavigationItemSelectedListener(item -> {
            mViewModel.changeTasksOption(item.getItemId());
            return true;
        });

        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);

        mViewModel.getTasks().observe(this, tasks -> {
            if (tasks != null) {
                tasksAdapterLive.setTasks(tasks);
                tasksAdapterLive.notifyDataSetChanged();
            }
        });

        mViewModel.changeTasksOption(mNavigationView.getSelectedItemId());
    }

Как видите, я решил использовать МедиаторLiveData в своей модели представления. Моя основная цель - изменить данные внутри адаптера, когда изменитьTasksOption() вызывается из фрагмента.

Я использую removeSource(), потому что, насколько я понимаю, он убирает источник LiveData из наблюдения. Но в моем случае это не так.

Когда я запускаю приложение, журналы:

MainViewModel: Add source active
MainViewModel: Add source: active - setValue

Когда я пытаюсь переключиться на другой источник - журналы

MainViewModel: Add source: all
MainViewModel: Add source: all - setValue
MainViewModel: Add source: active - setValue
MainViewModel: Add source: all - setValue
MainViewModel: Add source: active - setValue
*** repeats about 100 times

RecyclerView мигает

Итак, прошу любезно. Что я делаю неправильно? Я неправильно понял документацию? Что на самом деле делает removeSourse()? Потому что в моем случае он не удаляет источники.

В случае, если мой метод реализации этого неверен, как вы предлагаете мне поступить?

Спасибо!

ИЗМЕНЕНО:

Поэкспериментировав пару часов, я нашел решение. Да, это плохо (а может и нет?). Но явно это не универсально, т.к. Romm+LiveData не используем

Создайте обычные функции Room, которые возвращают список

@Query("SELECT * FROM tasks WHERE completed = 0")
List<Task> getActiveTasks();

@Query("SELECT * FROM tasks")
List<Task> getAllTasks();

@Query("SELECT * FROM tasks WHERE completed = 1")
List<Task> getClosedTasks();

Создал MutableLiveData в репо

private MutableLiveData<List<Task>> mTasksTestActive, mTasksTestAll, mTasksTestClosed;

Добавьте эти функции в репо

public LiveData<List<Task>> getActiveTasksTest() {
    Executors.newSingleThreadExecutor().execute(() -> {
        List<Task> taskList = mTaskDao.getActiveTasks();
        mTasksTestActive.postValue(taskList);
    });
    return mTasksTestActive;
}

public LiveData<List<Task>> getAllTasksTest() {
    Executors.newSingleThreadExecutor().execute(() -> {
        List<Task> taskList = mTaskDao.getAllTasks();
        mTasksTestAll.postValue(taskList);
    });
    return mTasksTestAll;
}

public LiveData<List<Task>> getClosedTasksTest() {
    Executors.newSingleThreadExecutor().execute(() -> {
        List<Task> taskList = mTaskDao.getClosedTasks();
        mTasksTestClosed.postValue(taskList);
    });
    return mTasksTestClosed;
}

Изменения ViewModel:

public void changeTasksOption(int index) {
    mMediatorTasks.removeSource(mData.getAllTasksTest());
    mMediatorTasks.removeSource(mData.getActiveTasksTest());
    mMediatorTasks.removeSource(mData.getClosedTasksTest());
    if (index == R.id.navigation_all) {
        Log.i(TAG, "Add source: all");
        mMediatorTasks.addSource(mData.getAllTasksTest(), tasks -> {
            Log.i(TAG, "Add source: all - postValue");
            mMediatorTasks.postValue(tasks);
        });
    } else if (index == R.id.navigation_closed) {
        Log.i(TAG, "Add source closed");
        mMediatorTasks.addSource(mData.getClosedTasksTest(), tasks -> {
            Log.i(TAG, "Add source: closed - postValue");
            mMediatorTasks.postValue(tasks);
        });
    } else {
        Log.i(TAG, "Add source active");
        mMediatorTasks.addSource(mData.getActiveTasksTest(), tasks -> {
            Log.i(TAG, "Add source: active - postValue");

            mMediatorTasks.postValue(tasks);
        });
    }
}

И теперь, переключив интерфейс, я получил свой результат. Нет больше циклов, и все, кажется, идет нормально.

Но все равно! Это плохое решение. Может быть, что-то не так с номером?

Maybe something is wrong with Room? нет, вам просто нужен другой способ связать выбранный параметр с выбранным LiveData, выставленным из комнаты.
EpicPandaForce 28.01.2019 18:05
4
1
1 272
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы возвращаете значения из своего репо синхронно в своем предыдущем коде репо -

public LiveData<List<Task>> getActiveTasks() {
    return mTaskDao.getActiveTasksLiveData();
}

public LiveData<List<Task>> getAllTasks() {
    return mTaskDao.getAllTasksLiveData();
}

public LiveData<List<Task>> getClosedTasks() {
    return mTaskDao.getClosedTasksLiveData();
}

Поэтому, когда вы вызываете removeSource(mData.getAllTasksTest()), он синхронно извлекает данные из репозитория, поэтому вы получаете данные из всех репозиториев.

В отредактированном коде вы используете рабочий поток для извлечения данных, что означает, что ваши исходные живые данные удаляются из живых данных посредника до того, как репо вернет какое-либо значение.

Спасибо за ответ! Но почему removeSourse на самом деле не удаляет его?

Serhii Pokrovskyi 28.01.2019 17:49

Это происходит, но после вызова функции mData.getAllTasksTest() внутри removeSource(mData.getAllTasksTest()). Таким образом, сначала вы получаете данные, а затем livedata удаляется. Попробуйте новое репо с предыдущим кодом.

Vishal Arora 28.01.2019 17:51
Ответ принят как подходящий
public void changeTasksOption(int index){
    mMediatorTasks.removeSource(mData.getAllTasks());
    mMediatorTasks.removeSource(mData.getActiveTasks());
    mMediatorTasks.removeSource(mData.getClosedTasks());

Нет, это не так, как должно быть!

Выбранный вариант должен быть в LiveData. Затем вы можете использовать Transformations.switchMap { против этих LiveData, чтобы выбрать правильный LiveData<List<Task>>.

private MutableLiveData<Integer> mSelectedIndex = new MutableLiveData<>();

private final LiveData<List<Task>> mMediatorTasks = Transformations.switchMap(mSelectedIndex, (index) -> {
    if (index == R.id.navigation_all) {
        return mData.getAllTasksTest();
    } else if (index == R.id.navigation_closed) {
        return mData.getClosedTasksTest();
    } else {
        return mData.getActiveTasksTest();
    }
});

public void changeTasksOption(int index) {
    mSelectedIndex.setValue(index);
}

public LiveData<List<Task>> getTasks(){
    return mMediatorTasks;
}

Кроме того, вы должны принести свои методы mData.get*(), чтобы снова вернуть LiveData<List<Task>> из DAO, это было лучшим решением.

Большое спасибо! Теперь понял!

Serhii Pokrovskyi 28.01.2019 18:49

@EpicPandaForce У меня есть аналогичный вариант использования. Работает ли Transformations.switchMap() в фоновом потоке для получения данных о задачах? Если нет, а списки большие, то не может ли вызов switchMap() блокировать основной поток? И используется ли setValue() в основном потоке, потому что этот вызов относится только к int, поэтому не должен блокироваться?

AJW 22.05.2021 03:16

Другие вопросы по теме