Dao 方法 returns List<String> 而我需要一个 Map<String,Integer>

Dao method returns List<String> while I need a Map<String,Integer>

在使用架构组件的 Android 应用程序中,我有以下视图模型:

public class MainViewModel extends AndroidViewModel {
    private final MutableLiveData<List<String>> mUnchecked = new MutableLiveData<>();
    private LiveData<List<String>> mChecked;

    public void setUnchecked(List<String> list) {
        mUnchecked.setValue(list);
    }

    public LiveData<List<String>> getChecked() { // OBSERVED BY A FRAGMENT
        return mChecked;
    }

    public MainViewModel(Application app) {
        super(app);
        mChecked = Transformations.switchMap(mUnchecked, 
                 list-> myDao().checkWords(list));
    }

上面switchMap的目的是检查作为字符串列表传递的单词中的哪些确实存在于房间table:

@Dao
public interface MyDao {
    @Query("SELECT word FROM dictionary WHERE word IN (:words)")
    LiveData<List<String>> checkWords(List<String> words);

上面的代码很适合我!

但是我一直想要稍微不同的东西 -

而不是字符串列表,我更愿意传递字符串映射(单词)-> 整数(分数):

    public void setUnchecked(Map<String,Integer> map) {
        mUnchecked.setValue(map);
    }

整数是 中的单词分数。一旦 checkWords() 已经 return 编辑了结果,我想将房间 table 中未找到的单词的分数设置为 null,并将其他分数保留为他们是。

编程代码会很简单(遍历 mChecked.getValue() 并为在列表 return 中找不到的单词设置为 null 由 DAO 方法编辑)- 但是如何"marry" 它与我的 LiveData 成员?

TL;DR

我想更改我的视图模型以保存地图而不是列表:

public class MainViewModel extends AndroidViewModel {
    private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<>();
    private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();

    public void setUnchecked(Map<String,Integer> map) {
        mUnchecked.setValue(map);
    }

    public LiveData<Map<String,Integer>> getChecked() { // OBSERVED BY A FRAGMENT
        return mChecked;
    }

    public MainViewModel(Application app) {
        super(app);

        // HOW TO OBSERVE mUnchecked
        // AND RUN myDao().checkWords(new ArrayList<>(mUnchecked.getValue().keys()))
        // WRAPPED IN Executors.newSingleThreadScheduledExecutor().execute( ... )
        // AND THEN CALL mChecked.postValue() ?
    }

请问如何实现?我应该扩展 MutableLiveData 还是使用 MediatorLiveData 或者使用 Transformations.switchMap()?

更新:

明天试试下面的(今天晚上太晚了)-

Dao方法我会改成return一个列表而不是LiveData:

@Query("SELECT word FROM dictionary WHERE word IN (:words)")
List<String> checkWords(List<String> words);

然后我会尝试扩展 MutableLiveData:

private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<Map<String,Integer>>() {
    @Override
    public void setValue(Map<String,Integer> uncheckedMap) {
        super.setValue(uncheckedMap);

        Executors.newSingleThreadScheduledExecutor().execute(() -> {

            List<String> uncheckedList = new ArrayList<>(uncheckedMap.keySet());
            List<String> checkedList = WordsDatabase.getInstance(mApp).wordsDao().checkWords(uncheckedList);
            Map<String,Integer> checkedMap = new HashMap<>();
            for (String word: uncheckedList) {
                Integer score = (checkedList.contains(word) ? uncheckedMap.get(word) : null);
                checkedMap.put(word, score);
            }
            mChecked.postValue(checkedMap);
        });
    }
};

棘手的问题!

如果我们检查 Transformations.switchMap 的源代码,我们会看到:

1.) 它用 MediatorLiveData

包装提供的实时数据

2.) 如果包装的实时数据发出一个事件,那么它会调用一个函数来接收包装的实时数据的新值,并且 returns 一个 "new" 不同类型的实时数据

3.) 如果 "new" 不同类型的实时数据与前一个不同,则删除前一个的观察者,并将其添加到新的观察者中(这样你只观察最新的 LiveData,不要意外地观察到旧的)

考虑到这一点,我认为我们可以链接您的 switchMap 调用并在 myDao().checkWords(words) 发生变化时创建一个新的 LiveData。

LiveData<List<String>> foundInDb = Transformations.switchMap(mWords, words -> myDao().checkWords(words));
LiveData<Map<String, Integer>> found = Transformations.switchMap(foundInDb, (words) -> {
    MutableLiveData<Map<String, Integer>> scoreMap = new MutableLiveData<>();
    // calculate the score map from `words` list
    scoreMap.setValue(map);
    return scoreMap;
});
this.mFound = found;

不过,请验证我告诉你的是否正确。

此外,如果有一堆单词,请考虑使用一些异步机制和scoreMap.postValue(map)

好吧,你在更新中的内容可能会起作用,但我不会为每个 setValue() 调用创建一个新的 Executor — 只创建一个并在你的 MutableLiveData 子类。此外,根据您的 minSdkVersion,您可以在 HashMap 上使用一些 Java 8 的东西(例如,replaceAll())来稍微简化代码。

您可以使用 MediatorLiveData,但最终我认为这会产生更多代码,而不是更少。因此,虽然从纯度的角度来看 MediatorLiveData 是一个更好的答案,但这可能不是您使用它的好理由。

坦率地说,恕我直言,这类事情并不是 LiveData 的真正目的。如果这是我现在正在处理的代码,我会在其中大部分使用 RxJava,最后转换为 LiveData。而且,我会在存储库中尽可能多地拥有这些,而不是在视图模型中。虽然你的未检查到检查的东西将是一个棘手的 RxJava 链来解决,但我仍然更喜欢它而不是 MutableLiveData 子类。

EpicPandaForce 建议的是一种理想的 LiveData-only 方法,尽管我认为他没有完全正确地实现您的算法,而且我怀疑它是否可以轻松地适应您想要的算法.

不过,最后的决定归结为:谁会看到这段代码?

  • 如果此代码仅供您查看,或者将生活在一个尘土飞扬的 GitHub 仓库中,很少有人会看,那么如果您觉得您可以维护 MutableLiveData子类,我们真的不能抱怨。

  • 如果同事要审查此代码,请询问同事的想法。

  • 如果未来的雇主将审查此代码...请考虑 RxJava。是的,它有一个学习曲线,但是为了引起雇主的兴趣,他们会对你知道如何使用 RxJava 比你知道如何破解 LiveData 来获得什么印象更深刻你要。