Dao方法返回List<String>,但我需要一个Map<String,Integer>。

4
在使用架构组件的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的目的是检查作为字符串列表传递的单词中,哪些存在于Room表中:
@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()返回结果,我希望将那些在Room表中找不到的单词得分设置为null,而将其他单词的得分保留。

编程代码很简单(通过遍历mChecked.getValue()并将在DAO方法返回的列表中找不到的单词设置为null)- 但如何“与我的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方法我将更改为返回列表而不是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);
        });
    }
};

1
我并不完全理解你的算法。"unchecked" Map<String, Integer> 的键是单词吗?如果是这样,那么虽然我理解你想要将未被 checkWords() 返回的单词的 Integer 替换为 null,但是对于那些被 checkWords() 返回的单词,你想要发生什么呢?你会保留 Integer 吗?还是计算一个新值,只是碰巧不是 null - CommonsWare
感谢您查看我的问题!是的,确切地说:映射的键是单词,值是由类似于Scrabble游戏板上的单词形成的分数。一旦在Room表中检查了单词的存在,我想用null替换无效单词的分数,并保留其他分数不变。我正在考虑扩展MutableLiveData,不确定是否正确(请参见更新的问题)。 - Alexander Farber
2个回答

2
好的,您在更新中所做的可能有效,但我不会为每个setValue()调用创建一个新的Executor - 只需在MutableLiveData子类中创建一个并将其保留即可。此外,根据您的minSdkVersion,您可能需要使用一些Java 8的东西(例如HashMap上的replaceAll())来简化代码。
您可以使用MediatorLiveData,但最终我认为这将导致更多的代码而不是减少。因此,虽然从纯度的角度来看,MediatorLiveData是一个更好的答案,但这可能不是您使用它的好理由。
老实说,这种事情并不是LiveData真正设置的目的,在我看来。如果这是我正在处理的代码,我会在大部分代码中使用RxJava,最后转换为LiveData。而且,我会尽可能地将这些内容放在存储库中,而不是在视图模型中。虽然您的未经检查到已检查的内容可能是一个棘手的RxJava链,但我仍然更喜欢它而不是MutableLiveData子类。
EpicPandaForce建议的是一种理想的LiveData-only方法,尽管我认为他没有完全正确地实现您的算法,并且我怀疑它是否可以轻松地适应您所需的算法。
最终,决定有点取决于:谁会看到这段代码?
- 如果这段代码只供您自己查看,或者将存储在很少有人会查看的GitHub存储库中,则如果您觉得可以维护MutableLiveData子类,我们无法抱怨。 - 如果这段代码将由同事审查,请询问您的同事他们的想法。 - 如果这段代码将由潜在雇主审核...请考虑使用RxJava。是的,它有一个学习曲线,但出于吸引雇主的目的,他们将更喜欢您知道如何使用RxJava而不是知道如何使用LiveData来获取您想要的内容。

1

棘手的问题!

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

1.) 它将提供的LiveData包装在MediatorLiveData中

2.) 如果包装的LiveData发出事件,则调用接收包装的LiveData新值的函数,并返回一个不同类型的“新”LiveData

3.) 如果不同类型的“新”LiveData与以前的LiveData不同,则删除以前的观察者,并将其添加到新的LiveData中(这样您只观察最新的LiveData,不会意外地观察旧的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)

谢谢,我已尝试但无济于事。我能不能在我的视图模型中有两个 MutableLiveData<Map<String,Integer>> 成员?当其中一个的值被设置时,运行 myDao().checkWords(new ArrayList<>(...keys())),并用 Executors.newSingleThreadScheduledExecutor().execute(...)postValue 将其包装到另一个成员中? - Alexander Farber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接