Метод Dao возвращает List<String>, в то время как мне нужна карта<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 чтобы проверить, какие слова, переданные в виде списка строк, существуют в таблице 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() вернул результаты, я хотел бы установить null для слов, не найденных в таблице комнаты, и оставьте другие оценки, как они.

Программный код будет легко (перебрать mChecked.getValue() и установить в null для слов, не найденных в списке, возвращенном методом DAO) - но как "жениться" на нем с моим 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:

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

2 ответа

Решение

Ну, то, что у вас там в обновлении, вероятно, работает, хотя я бы не стал создавать новый Executor для каждого setValue() вызов - создайте только один и удерживайте его в своем MutableLiveData подкласс. Кроме того, в зависимости от вашего minSdkVersion, вы можете использовать некоторые вещи Java 8 на HashMap (например, replaceAll()) немного упростить код.

Вы могли бы использовать MediatorLiveDataХотя, в конце концов, я думаю, что это приведет к увеличению кода, а не к уменьшению. Итак, пока с точки зрения чистоты MediatorLiveData Это лучший ответ, который не может быть хорошей причиной для его использования.

Честно говоря, такого рода вещи не то, что LiveData действительно настроен на ИМХО. Если бы это был мой код, над которым я работал сейчас, я бы использовал RxJava для большей части этого, конвертируя в LiveData в конце. И у меня было бы как можно больше этого в репозитории, а не в модели представления. Хотя ваш непроверенный и проверенный материал будет хитрой цепочкой RxJava, я все равно предпочитаю это MutableLiveData подкласс.

То, что EpicPandaForce предлагает, является идеальным LiveData- только подход, хотя я не думаю, что он реализует ваш алгоритм достаточно правильно, и я скептически отношусь к тому, что его можно легко адаптировать к вашему желаемому алгоритму.

В конце концов, решение сводится к тому, кто увидит этот код?

  • Если этот код предназначен только для ваших глаз, или он будет жить в пыльном репозитории GitHub, на который мало кто может взглянуть, тогда, если вы чувствуете, что можете поддерживать MutableLiveData подкласс, мы не можем жаловаться.

  • Если этот код будет рассмотрен коллегами, спросите своих коллег, что они думают.

  • Если этот код будет рассмотрен потенциальными работодателями... рассмотрите RxJava. Да, у него есть кривая обучения, но в целях получения интереса со стороны работодателей, вы будете больше впечатлены тем, что вы знаете, как использовать RxJava, чем тем, как вы знаете, как взломать LiveData чтобы получить то, что вы хотите.

Вопрос на засыпку!

Если мы проверим исходный код Transformations.switchMap, Мы видим, что:

1.) он оборачивает предоставленные живые данные с MediatorLiveData

2.) если обернутые живые данные генерируют событие, то он вызывает функцию, которая получает новое значение обернутых живых данных, и возвращает "новые" живые данные другого типа.

3.) если "новые" живые данные другого типа отличаются от предыдущих, то наблюдатель предыдущего удаляется, а вместо него добавляется новый (так что вы наблюдаете только самые новые LiveData и не т случайно осмотрел старый)

Имея это в виду, я думаю, что мы можем связать ваши вызовы switchMap и создавать новые LiveData всякий раз, когда myDao().checkWords(words) изменения.

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),