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

Автор: admin от 12-03-2015, 16:20, посмотрело: 926

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

Работая с 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 вместе с ним. Однако одновременное их использование дает приемлемые результаты в упрощении структуры поддерживаемого приложения.

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

    Категория: Веб-разработка, Android

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

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

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