#rx-java
#rx-java
Вопрос:
С предстоящим выпуском RxJava2 одним из важных изменений является то, что null
он больше не принимается в качестве элемента потока, т.Е. Следующий код будет генерировать исключение: Observable.just(null)
Честно говоря, у меня смешанные чувства по поводу этого изменения, и часть меня понимает, что это приведет к принудительному использованию чистых API, но я вижу ряд случаев использования, когда это может быть проблемой.
Например, в моем приложении у меня есть кэш в памяти:
@Nullable CacheItem findCacheItem(long id);
CacheItem может отсутствовать в кэше, поэтому метод может возвращать нулевое значение.
Способ его использования с Rx * — заключается в следующем:
Observable<CacheItem> getStream(final long id) {
return Observable.fromCallable(new Callable<CacheItem>() {
@Override public CacheItem call() throws Exception {
return findCacheItem(id);
}
});
}
Таким образом, при таком подходе я могу получить null в своем потоке, что является полностью допустимой ситуацией, поэтому оно обрабатывается должным образом на принимающей стороне — допустим, пользовательский интерфейс изменяет свое состояние, если элемент отсутствует в кэше:
Observable.just(user)
.map(user -> user.getName())
.map(name -> convertNameToId(name))
.flatMap(id -> getStream(id))
.map(cacheItem -> getUserInfoFromCacheItem(cacheItem))
.subscribe(
userInfo -> {
if(userInfo != null) showUserInfo();
else showPrompt();
}
);
С RxJava2 мне больше не разрешено публиковать null
поток, поэтому мне нужно либо обернуть мой CacheItem в какой-то другой класс и заставить мой поток создавать эту оболочку вместо этого, либо внести довольно большие архитектурные изменения.
Перенос каждого отдельного элемента потока в обнуляемый аналог мне кажется неправильным.
Я упускаю что-то фундаментальное здесь?
Похоже, что ситуация, подобная моей, довольно популярна, поэтому мне любопытно, какова рекомендуемая стратегия решения этой проблемы, учитывая новую политику «no null» в RxJava2?
РЕДАКТИРОВАТЬ Пожалуйста, смотрите последующее обсуждение в репозитории RxJava на GitHub
Ответ №1:
Ну, есть несколько способов представить то, что вы хотите.
Одним из вариантов является использование Observable<Optional<CacheItem>>
:
Observable<Optional<CacheItem>> getStream(final long id) {
return Observable.defer(() -> {
return Observable.just(Optional.ofNullable(findCacheItem(id)));
});
}
public static <T> Transformer<Optional<T>, T> deoptionalize() {
return src ->
src.flatMap(item -> item.isPresent()
? Observable.just(item.get())
: Observable.empty();
}
Затем вы используете .compose(deoptionalize())
для сопоставления необязательного с необязательным наблюдаемым.
Ответ №2:
В качестве другого решения вы можете добавить статический экземпляр CacheItem.NULL и вернуть его подписчику, когда нет кэшированных данных
Single
.concat(loadFromMemory(), loadFromDb(), loadFromServer())
.takeFirst { it != CachedItem.NULL }
.subscribe(
Ответ №3:
Вы можете использовать RxJava2-Nullable
для обработки нулевого значения в RxJava2.
Для вашей ситуации вы можете сделать:
Observable<CacheItem> getStream(final long id) {
return RxNullable.fromCallable(() -> findCacheItem(id))
.onNullDrop()
.observable();
}
Чтобы вызвать showPrompt
, когда оно равно null, вы можете сделать:
Observable.just(user)
.map(user -> user.getName())
.map(name -> convertNameToId(name))
.flatMap(id -> getStream(id).onNullRun(() -> showPrompt()))
.map(cacheItem -> getUserInfoFromCacheItem(cacheItem))
.subscribe(userInfo -> showUserInfo());
NullableObservable<CacheItem> getStream(final long id) {
return RxNullable.fromCallable(() -> findCacheItem(id)).observable();
}
Ответ №4:
Возможное решение — использовать Maybe.switchIfEmpty
Пример:
public static <T> Maybe<T> maybeOfNullable(T value) {
return value == null ? Maybe.empty() : Maybe.just(value);
}
maybeOfNullable(user)
.map(user -> user.getName())
.map(name -> convertNameToId(name))
.flatMap(id -> getStream(id))
.map(cacheItem -> getUserInfoFromCacheItem(cacheItem))
// perform another action in case there are no any non null item emitted
.switchIfEmpty(Maybe.fromAction(() -> showPrompt()))
.subscribe(userInfo -> showUserInfo());
Ответ №5:
Nulls
странные вещи. С одной стороны, они не отмечают полный успех, как это делает конкретное значение. С другой стороны, они не сигнализируют о полном сбое, как Exception
это происходит. Таким образом, можно сказать, что null
это на полпути между счастливым путем и путем отказа. Всякий null
раз, когда это запрещено, это заставляет вас «принять решение» и переместить его в одну из групп. Либо сделайте это полноценным успехом (обернув его в какой-то объект и поместив логику обработки null в этот объект), либо полной ошибкой и выбросьте ее как Exception
.
Для меня оказалось очень выгодно ограничить счастливый путь к полному успеху и обрабатывать все отклонения в onError
канале:
Observable.just(user)
.map(user -> user.getName())
.map(name -> convertNameToId(name))
.flatMap(id -> getStream(id))
.map(cacheItem -> getUserInfoFromCacheItem(cacheItem)) //throws custom exception
.subscribe(
userInfo -> {
showUserInfo(userInfo);
},
ex -> {
showPrompt(ex); //handles the custom exception
}
);
Лучшая часть заключается в том, что onError
происходит короткое замыкание всех преобразований (которые обычно зависят от значения, отличного от null) вплоть до момента, когда вы решите, что справитесь с этим. Это либо subscribe/onError
в конце, либо onErrorResumeNext
где-то в середине цепочки. Имейте в виду, что разрешение onError
распространяться полностью завершает цепочку.