【问题标题】:Filling an adapter from different LiveData sources从不同的 LiveData 源填充适配器
【发布时间】:2019-06-21 13:02:31
【问题描述】:

我正在玩 LiveData,想了解它可以做什么。 我想通过开关(如果你喜欢使用过滤器)用来自不同来源的数据填充我的 RecyclerView。

在适配器内过滤值不是一种选择。 所以,我决定在我的视图模型中使用 MediatorLiveData

道:

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

视图模型

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

如您所见,我决定在我的视图模型中使用 MediatorLiveData。 我的主要目标 - 当 changeTasksOption() 从片段调用时更改适配器内的数据。

我使用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

创建返回 List 的普通 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();

在 repo 中创建 MutableLiveData

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

将这些函数添加到 repo

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

现在,通过切换 UI,我得到了我的结果。没有更多的循环,一切似乎都正常。

但还是!这是一个糟糕的解决方案。可能是 Room 出了点问题?

【问题讨论】:

  • Maybe something is wrong with Room? 不,您只需要一种不同的方式将选定的选项链接到从 Room 公开的选定 LiveData

标签: android android-recyclerview android-room android-livedata android-viewmodel


【解决方案1】:

您在以前的回购代码中同步地从您的回购中返回值 -

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。使用以前的代码尝试您的新存储库。
【解决方案2】:
public void changeTasksOption(int index){
    mMediatorTasks.removeSource(mData.getAllTasks());
    mMediatorTasks.removeSource(mData.getActiveTasks());
    mMediatorTasks.removeSource(mData.getClosedTasks());

不,这不应该是这样!

所选选项应位于 LiveData 中。然后您可以针对该 LiveData 使用 Transformations.switchMap { 来选择正确的 LiveData&lt;List&lt;Task&gt;&gt;

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*() 方法再次从 DAO 返回 LiveData&lt;List&lt;Task&gt;&gt;,这是一个更好的解决方案。

【讨论】:

  • 非常感谢!现在明白了!
  • @EpicPandaForce 我有一个类似的用例。 Transformations.switchMap() 是否在后台线程上运行以获取任务数据?如果不是并且列表很大,那么 switchMap() 调用不能在主线程上阻塞吗?并且在主线程上使用 setValue() 是因为该调用仅在 int 上,因此不应阻塞?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-20
  • 2020-04-08
  • 1970-01-01
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多