[Из песочницы] Android. Пару слов об MVP + rxJava

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

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

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

public interface ExampleModel { Observable changeText (); Observable 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 changeText () { return WidgetObservable .text (viewHolder.editText) .map (new Func1() { @Override public String call (OnTextChangeEvent event) { return event.toString ().trim (); } }); }

@Override public Observable 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>() { @Override public Observable call (String query) { return model.request (query); } }) .subscribe (new Action1() { @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 вместе с ним. Однако одновременное их использование дает приемлемые результаты в упрощении структуры поддерживаемого приложения.

© Habrahabr.ru