» » » Реактивные приложения с Model-View-Intent. Часть 2: View и Intent

 

Реактивные приложения с Model-View-Intent. Часть 2: View и Intent

Автор: admin от 25-09-2017, 16:40, посмотрело: 420

В первой части мы обсудили что такое модель, ее связь с состоянием и то, как правильно спроектированная модель помогает решить некоторые проблемы в разработке под Android. В этой статье мы продолжим наш путь к созданию реактивных приложений с помощью паттерна Model-View-Intent.



Прежде, чем начать, вкратце обсудим основную идею MVI.



Model-View-Intent (MVI)



Этот паттерн был описан Андре Штальтцем (Andre Staltz) для javascript-фреймворка cycle.js. С теоретической и математической точки зрения MVI можно описать следующим образом:



Реактивные приложения с Model-View-Intent. Часть 2: View и Intent


GitHub.



Начнем с реализации экрана поиска. Первым делом я определяю модель, которая будет отображена с помощью View, как описано в первой части этого цикла статей. Все классы моделей я буду писать с суффиксом ViewState, поскольку модель отражает состояние.





Java – строго типизированный язык, поэтому я выбрал типобезопасный подход к созданию модели, разделив каждое подсостояние внутри класса. Бизнес-логика будет возвращать объект типа SearchViewState, который может быть экземпляром SearchViewState.Error и т.д. Это мое личное предпочтение, вы можете проектировать модель по-своему.



Сосредоточимся на бизнес–логике. Создадим SearchInteractor, который будет отвечать за поиск. Результатом выполнения будет объект SearchViewState.





Посмотрим на сигнатуру метода SearchInteractor.search(): есть входной параметр searchString и выходной параметр Observable. Это говорит о том, что на наблюдаемом потоке мы ожидаем произвольное количество экземпляров SearchViewState. Метод startWith() нужен для того, чтобы заэмитить SearchViewState.Loading перед тем, как начать поисковый запрос. Тогда View сможет показать progressBar во время выполнения поиска.



Метод onerrorReturn() ловит любые исключения, которые могут возникнуть во время выполнения поиска, и эмитит SearchViewState.Error. Мы не можем просто использовать колбэк onerror() при подписке на Observable. Это распространенное заблуждение в RxJava: колбэк onerror() нужно использовать тогда, когда весь наблюдаемый поток наталкивается на неустранимые ошибки и весь поток завершается.



В нашем случае ошибка отсутствия подключения к интернету не попадает под определение неустранимых ошибок – это просто одно из состояний нашей модели. Кроме того, мы сможем переключиться на другое состояние – SearchViewState.Loading, – после того, как подключение к интернету снова будет доступно.



Таким образом мы создаем наблюдаемый поток из бизнес–логики во View, который эмитит новую модель каждый раз, когда изменяется состояние. Нам не нужно, чтобы наблюдаемый поток завершался при ошибке подключения к интернету, поэтому такие ошибки обрабатываются как состояние. Обычно в MVI наблюдаемый поток никогда не завершается (не вызываются методы onComplete или onerror()).



Подводя итог: SearchInteractor предоставляет наблюдаемый поток Observable и эмитит новый SearchViewState каждый раз при изменении состояния.



Рассмотрим, как выглядит слой View, который должен отобразить модель. Ранее я предложил, чтобы View имела функцию render(model). Кроме того, View должна предоставлять возможность другим слоям реагировать на события UI. В MVI эти события называются интенты. В нашем случае есть только один интент: пользователь ищет продукт путем ввода поискового запроса в поле поиска. В MVP есть хорошая практика создавать интерфейс для слоя View, поступим также и для MVI.



public interface SearchView {
   Observable<String> searchIntent();
   void render(SearchViewState viewState);
}


В нашем случае View предоставляет только один интент, но в зависимости от задачи их может быть несколько.



В первой части мы обсудили, почему использование единственного метода render() является хорошим решением. Перед созданием конкретной реализации слоя View посмотрим на то, как будет выглядеть окончательный вариант:









Метод render(SearchViewState) должен выглядеть лаконично. В searchIntent() я использую библиотеку RxBindings. RxSearchView.queryText() создает Observable, который эмитит строку каждый раз, когда пользователь что-то вводит в виджет EditText. Я использую filter(), чтобы поисковый запрос начинался после ввода трех символов и более. Нам не нужно, чтобы поисковый запрос отправлялся на сервер каждый раз, когда пользователь вводит новый символ, поэтому я добавил оператор debounce().



Мы знаем, что входной поток данных для данного экрана – метод searchIntent(), а выходной поток данных – метод render().



Следующее видео наглядно демонстрирует, как происходит взаимодействие между двумя этими потоками.







Остался вопрос, как связать интент и бизнес–логику? Если вы внимательно посмотрите видео, то увидите оператор flatMap() посередине. Это указывает на наличие дополнительного компонента, о котором я не говорил – Presenter, – отвечающего за соединения слоев.



public class SearchPresenter extends MviBasePresenter<SearchView, SearchViewState> {
   private final SearchInteractor searchInteractor;

   @Override protected void bindIntents() {
       Observable<SearchViewState> search =
               intent(SearchView::searchIntent)
                       .switchMap(searchInteractor::search) // на видео я использовал flatMap(), но здесь имеет смысл использовать switchMap()
                       .observeOn(AndroidSchedulers.mainThread());

       subscribeViewState(search, SearchView::render);
   }
}


Что такое MviBasePresenter, методы intent() и subscribeViewState(). Этот класс – часть библиотеки Mosby. Стоит сказать несколько слов о Mosby и о том, как работает MviBasePresenter. Начнем с жизненного цикла: у MviBasePresenter его нет. Метод bindIntent() связывает интент из View с бизнес–логикой. Как правило для пересылки интента в бизнес-логику используется flatMap() или switchMap(). Этот метод вызывается единожды, когда View присоединяется к Presenter, но не вызывается после того, как View вновь присоединится к Presenter, например, после изменение ориентации экрана.



Может возникнуть вопрос, действительно ли MviBasePresenter может пережить изменение ориентации экрана, и если да, то каким образом Mosby гарантирует, что наблюдаемый поток не “утекает”? Для этого и предназначены методы intent() и subscribeViewState().



intent() создает PublishSubject внутри Presenter и использует его как “шлюз” для бизнес–логики. PublishSubject подписывается на интент View. Вызов интента (О1) на самом деле возвращает PublishSubject, который подписан на О1.



После изменения ориентации экрана Mosby отсоединяет View от Presenter, но только временно отписывает внутренний PublishSubject из View и повторно переподпиcывает PublishSubject на интент View, когда View вновь присоединяется к Presenter.



subscribeViewState() делает то же самое в обратную сторону. Он создает внутри Presenter BehaviorSubject в качестве “шлюза” от бизнес–логики ко View. Так как это BehaviorSubject, мы можем получить обновленную модель из бизнес–логики даже когда View отсоединена от Presenter. BehaviorSubject всегда хранит последнее значение, которое он получил, и повторяет его, когда View вновь присоеденится к Presenter.



Простое правило: используйте метод intent() чтобы обернуть любой интент. Используйте subscribeViewState() вместо Observable.subscribe(…).



Реактивные приложения с Model-View-Intent. Часть 2: View и Intent



Как насчет других событий жизненного цикла, например, onPause() или onResume()? Я по–прежнему считаю, что презентеру не нужны события жизненного цикла. Однако если вы действительно думаете, что они вам нужны, то можете создать их как интент. В вашем View появится pauseIntent(), запуск которого инициирует жизненный цикл Android, а не действие пользователя.



Заключение



В этой части мы поговорили об основах MVI и реализовали простой экран. Вероятно, этот пример слишком прост, чтобы понять все преимущества MVI. Нет ничего плохого в MVP или MVVM, и я не говорю, что MVI лучше, чем другие архитектурные шаблоны. Тем не менее, я считаю, что MVI помогает нам писать более элегантный код для сложных проблем, как мы увидим в следующей части, в которой поговорим о state reducer.


Источник: Хабрахабр

Категория: Операционные системы » Android

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *