Android. Пару слов об MVP + rxJava

от автора

Работая с Android часто можно видеть, как весь функциональный код помещается в методы жизненного цикла activity/fragment. В общем-то такой подход имеет некоторое обоснование — «методы жизненного цикла» всего лишь хэндлеры, обрабатывающие этапы создания компонента системой и специально предназначенные для наполнения их кодом. Добавив сюда то, что каркас UI описывается через xml файлы, мы уже получаем базовое разделение логики и интерфейса. Однако из-за не совсем «изящной» структуры жизненного цикла, его зависимости от множества флагов запуска, и различной (хоть и похожей) структуры для разных компонентов, эффективно воспользоваться подобным разделением не всегда бывает возможно, что в итоге выливается в написании всего кода в onCreate().

Model-View-Presenter+rxJava

MVP паттерн разработки для android, предлагающий разбивать приложение на следующие части:

  1. Model — представляет из себя точку входа к данным приложения (часто на каждый экран своя модель). При этом особой разницы откуда данные быть не должно — данные сетевых запросов или данные взаимодействия пользователя с UI (клики, свайпы и т.д). Хорошее место для внедрения «рукописных» кэшей. В связке с rxJava будет представлять из себя набор методов, отдающих Observable.
  2. View — представляет из себя класс, устанавливающий состояние UI элементов. Не путать термин с android.view.View
  3. Presenter — устанавливает связь между обработкой данных, получаемых из Model и вызовом методов у View, реализуя тем самым реакцию UI компонентов на на данные. Методы Presenter вызываются из методов жизненного цикла activity/fragment и часто «симметричны» им.

Model/View/Presenter должны представлять из себя интерфейсы для большей гибкости модификации кода.

Пример

Рассмотрим пример приложения, состоящего из одного экрана, на котором находится EditText и TextView. При этом по мере редактирования текста в EditText отправляются сетевые запросы, результат которых должен отображаться в TextView (конкретика запроса не должна нас волновать, это может быть перевод, краткая справка по термину или что то подобное).

ExampleModel.java:

public interface ExampleModel {       Observable<String> changeText();       Observable<String> request(String query);   }   

ExampleView.java:

public interface ExampleView {       void showResponse(String result);   }   

ExamplePresenter.java:

public interface ExamplePresenter {       void onCreate(Activity activity, Bundle savedInstanceState); }   

Реализация

Так как Model и View используют одни и тебе виджеты (в нашем случае EditText и TextView) для своей работы, разумно будет реализовать содержащий их класс.

ExampleViewHolder.java:

public class ExampleViewHolder {       public final EditText editText;       public final TextView textView;        public ExampleViewHolder(EditText editText, TextView textView) {           this.editText = editText;           this.textView = textView;       }   }   

При реализации Model мы предполагаем использование rxAndroid, для «оборачивания» EditTetx, и retrofit для реализации сетевых запросов.

ExampleModelImpl.java:

public class ExampleModelImpl implements ExampleModel {       private final ExampleViewHolder viewHolder;         public ExampleModelImpl(final ExampleViewHolder viewHolder) {           this.viewHolder = viewHolder;       }      @Override       public Observable<String> changeText() {           return WidgetObservable               .text(viewHolder.editText)               .map(new Func1<OnTextChangeEvent, String>() {                   @Override                   public String call(OnTextChangeEvent event) {                       return event.toString().trim();                   }               });       }        @Override       public Observable<String> request(String query) {         //всю работу берет на себя retrofit           return RestManager.newInstance().request(query);      }   }  

ExampleViewImpl.java:

public class ExampleViewImpl implements ExampleView {       private final ExampleViewHolder viewHolder;               public ExampleViewImpl(final ExampleViewHolder viewHolder) {           this.viewHolder = viewHolder;       }           @Override       public void showResponse(final String result) {           viewHolder.textView.setText(result);       }   }  

Так как количество сетевых запросов зависит от скорости набора текста (а она может быть достаточно высока), существует естественное желание ограничить частоту событий редактирование текста в EditText. В данном случае это реализуется директивой debounce (при этом, естественно, ввод текста не блокируется, а лишь пропускается часть событий редактирования, произошедших в временной промежуток в 150 миллисекунд).

ExamplePresenterImpl.java:

public class ExamplePresenterImpl implements ExamplePresenter {       private final ExampleModel model;       private final ExampleView view;       private Subscription subscription;        public ExamplePresenterImpl(ExampleModel model, ExampleView view) {           this.model = model;           this.view = view;       }        @Override       public void onCreate(Activity activity, Bundle savedInstanceState) {           subscription = model               .changeText()               //ограничивает частоту событий             .debounce(150, TimeUnit.MILLISECONDS)               .switchMap(new Func1<String, Observable<String>>() {                   @Override                    public Observable<String> call(String query) {                        return model.request(query);                    }               })            .subscribe(new Action1<String>() {                  @Override                  public void call(String result) {                      view.showResponse(result);                  }                 });       }          @Override       public void onDestroy() {           if (subscription != null) {               subscription.unsubscribe();            }       }   }   

Реализация activity, передающая всю сущностную часть работы Presenter:

ExampleActivity.java

public class ExampleActivity extends Activity {       private ExamplePresenter examplePresenter;        @Override       protected void onCreate(Bundle savedInstanceState) {           super.onCreate(savedInstanceState);           setContentView(R.layout.example_activity);            final ExampleViewHolder exampleViewHolder = new ExampleViewHolder(             (TextView) findViewById(R.id.text_view),               (EditText) findViewById(R.id.edit_text)           );            final ExampleModel exampleModel              = new ExampleModelImpl(exampleViewHolder);            final ExampleView exampleView              = new ExampleViewImpl(exampleViewHolder);            examplePresenter            = new ExamplePresenterImpl(exampleModel, exampleView);                   examplePresenter.onCreate(this, savedInstanceState);       }        @Override       protected void onDestroy() {           super.onDestroy();           examplePresenter.onDestroy();       }   } 

Заключение

Хотя наш пример невероятно упрощен, в нем уже есть нетривиальные моменты связанные с контролем частоты событий. Представить же эволюцию нашего приложения в разрезе mvp довольно легко:

  • Обработка отсутствие сети — решается на уровне Model-и и View.
    Кэширование результатов запросов решается на уровне Model (можно на уровне retrofit, путем настройки okhttp.Cache или HttpResponsecache — в зависимости от того, что используется).
  • Общая обработка ошибок решается на уровне Presenter добавлением обработчика ошибок при subscribe.
  • Логирование решается в зависимости от того, что надо логировать.
  • Создание более сложного UI, возможно анимации — нужно модифицировать ViewHolder, View.

Эпилог

MVP — не единственный способ разбиения Android-приложения на компоненты, и уж тем более он не предполагает обязательного использования rxJava вместе с ним. Однако одновременное их использование дает приемлемые результаты в упрощении структуры поддерживаемого приложения.

ссылка на оригинал статьи http://habrahabr.ru/post/252903/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *