[Из песочницы] Android. Пару слов об MVP + rxJava
Работая с 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
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
@Override
public Observable
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
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 вместе с ним. Однако одновременное их использование дает приемлемые результаты в упрощении структуры поддерживаемого приложения.