Работая с Android часто можно видеть, как весь функциональный код помещается в методы жизненного цикла activity/fragment. В общем-то такой подход имеет некоторое обоснование — «методы жизненного цикла» всего лишь хэндлеры, обрабатывающие этапы создания компонента системой и специально предназначенные для наполнения их кодом. Добавив сюда то, что каркас UI описывается через xml файлы, мы уже получаем базовое разделение логики и интерфейса. Однако из-за не совсем «изящной» структуры жизненного цикла, его зависимости от множества флагов запуска, и различной (хоть и похожей) структуры для разных компонентов, эффективно воспользоваться подобным разделением не всегда бывает возможно, что в итоге выливается в написании всего кода в onCreate().
Model-View-Presenter+rxJava
MVP паттерн разработки для 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<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/
Добавить комментарий