【问题标题】:Dao method returns List<String> while I need a Map<String,Integer>Dao 方法返回 List<String> 而我需要一个 Map<String,Integer>
【发布时间】:2018-12-16 17:46:33
【问题描述】:

在使用架构组件的 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);
    }

整数将是my game 中的单词分数。一旦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()?

更新:

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

我将改成返回列表而不是LiveData的Dao方法:

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

【问题讨论】:

  • 我不完全理解你的算法。是“未选中”Map&lt;String, Integer&gt; 单词的键吗?如果是这样,那么虽然我知道你想用null 替换checkWords() 未返回的单词的Integer,但是对于@返回的单词are,你想要发生什么987654344@?您要单独留下Integer 吗?还是您正在计算一个恰好不是 null 的新值?
  • 感谢您查看我的问题!是的,完全正确:地图的键是单词,值是由Scrabble-like 游戏板上的单词形成的分数。一旦在 Room 表中检查了单词的存在,我想将无效单词的分数替换为 null 并保留其他分数。我正在考虑扩展MutableLiveData,不确定这是否是正确的路径(请参阅更新后的问题)。

标签: android android-room android-architecture-components android-livedata


【解决方案1】:

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

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

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

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

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

    1234563抱怨。
  • 如果此代码将由同事审核,请询问您的同事的想法。

  • 如果此代码将由潜在雇主审查...考虑 RxJava。是的,它有一个学习曲线,但为了引起雇主的兴趣,他们会更喜欢你知道如何使用 RxJava,而不是知道如何破解 LiveData 来获得你想要的东西。

【讨论】:

    【解决方案2】:

    棘手的问题!

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

    1.) 它使用 MediatorLiveData 包装提供的实时数据

    2.) 如果被包装的实时数据发出一个事件,那么它调用一个函数来接收被包装的实时数据的新值,并返回一个不同类型的“新”实时数据

    3.) 如果不同类型的“新”实时数据与前一个不同,则删除前一个的观察者,并将其添加到新的观察者中(这样您就只能观察到最新的 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&lt;Map&lt;String,Integer&gt;&gt; 成员吗?当一个值被设置时,运行 myDao().checkWords(new ArrayList&lt;&gt;(...keys())) 包裹在一个 Executors.newSingleThreadScheduledExecutor().execute(...) 和 postValue 到另一个?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-09-30
    • 1970-01-01
    • 2023-03-26
    • 2017-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多