Метод Дао возвращает список <String>, а мне нужна карта - PullRequest
0 голосов
/ 16 декабря 2018

В приложении 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 - проверить, какое из слов, переданных в виде списка строк, сделатьсуществует в таблице комнат:

@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 для слов, не найденных в таблице Room, и оставить остальные баллы такими, какие они есть.

Программный код будетбыть простым (перебрать 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 ]

0 голосов
/ 19 декабря 2018

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

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

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

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

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

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

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

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

0 голосов
/ 17 декабря 2018

Сложный вопрос!

Если мы проверим исходный код на 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).

...