Метод 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)
,