Я играю с 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);
});
}
}
И теперь, переключив интерфейс, я получил свой результат. Нет больше циклов, и все, кажется, идет нормально.
Но все равно! Это плохое решение. Может быть, что-то не так с номером?
Вы возвращаете значения из своего репо синхронно в своем предыдущем коде репо -
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 на самом деле не удаляет его?
Это происходит, но после вызова функции mData.getAllTasksTest() внутри removeSource(mData.getAllTasksTest()). Таким образом, сначала вы получаете данные, а затем livedata удаляется. Попробуйте новое репо с предыдущим кодом.
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, это было лучшим решением.
Большое спасибо! Теперь понял!
@EpicPandaForce У меня есть аналогичный вариант использования. Работает ли Transformations.switchMap() в фоновом потоке для получения данных о задачах? Если нет, а списки большие, то не может ли вызов switchMap() блокировать основной поток? И используется ли setValue() в основном потоке, потому что этот вызов относится только к int, поэтому не должен блокироваться?
Maybe something is wrong with Room?нет, вам просто нужен другой способ связать выбранный параметр с выбранным LiveData, выставленным из комнаты.