«Как я научился не волноваться и полюбил android-mvp»,...
TRANSCRIPT
Почему MVP?
Важные характеристики системы:
- понятность кода
- расширяемость
- готовность к изменениям
- тестируемость
4
Почему MVP?
Важные характеристики системы:
- понятность кода
- расширяемость
- готовность к изменениям
- тестируемость
Это всё НЕ о традиционном подходе!
5
Почему MVP?
MVP / MVVM(С) / ... фундаментально об одном и том же:
Добиваться повышения качества системы за счет разбиения её на слои с четко выраженными обязанностями и абстрагирования этих слоев друг от друга.
/* If you put ten software architects into a room and have them discuss what the Model-View-Controller pattern is, you will end up with twelve different opinions.(с) */
6
Почему MVP?
MVP / MVVM(C) / ... фундаментально об одном и том же:
Добиваться повышения качества системы за счет разбиения её на слои с четко выраженными обязанностями и абстрагирования этих слоев друг от друга.
MVP:
+ легче поддается тестированию
- требует больше кода
/* If you put ten software architects into a room and have them discuss what the Model-View-Controller pattern is, you will end up with twelve different opinions.(с) */
7
Android-MVP: описание MVP
Смысл:
отделить представление от логики
Для Android:
помогает решить проблему, когда Activity выступает в роли God Object
9
Android-MVP: описание MVP
VIEW PRESENTER MODEL
оповестить о событии
запросить данные
обновить UI получить данные
View максимально прост и пассивен Model включает в себя слой получения данных и бизнес-логики Presenter получает данные из Model, трансформирует их, отдает во View; решает, что делает View.
10
Android-MVP: MVP + Clean architecture
VIEW PRESENTER INTERACTOR REPOSITORY
ENTITY1
ENTITY2
VIEWMODEL
V P M
DOMAIN LAYER DATA LAYER
11
Android-MVP: model public interface Api { @GET("weather") Observable<WeatherResponse> get(@Query("APPID") String key, @Query("q") String q); }
REPOSITORY
ENTITY1
ENTITY2
13
Android-MVP: model public interface Api { @GET("weather") Observable<WeatherResponse> get(@Query("APPID") String key, @Query("q") String q); } public class WeatherResponse { Coord coord; List<Weather> weather; String base; Main main; Wind wind; Clouds clouds; double dt; Sys sys; int id; String name; int cod; }
14
REPOSITORY
ENTITY1
ENTITY2
Android-MVP: model public interface Api { @GET("weather") Observable<WeatherResponse> get(@Query("APPID") String key, @Query("q") String q); } public class WeatherResponse { Coord coord; List<Weather> weather; String base; Main main; Wind wind; Clouds clouds; double dt; Sys sys; int id; String name; int cod; }
public class Main { double temp; double pressure; double humidity; double tempMin; double tempMax; }
15
public class Wind { double speed; double deg; } ...
REPOSITORY
ENTITY1
ENTITY2
Android-MVP: model public interface WeatherRepository { Observable<WeatherResponse> getWeather(String city); }
16
REPOSITORY
ENTITY1
ENTITY2
Android-MVP: model public interface WeatherRepository { Observable<WeatherResponse> getWeather(String city); } public class WeatherRetrofitRepository implements WeatherRepository { Api api; public WeatherRetrofitRepository(Api api) { this.api = api; } @Override public Observable<WeatherResponse> getWeather(String city) { return api.get(BuildConfig.WEATHER_API_KEY, city); } }
17
REPOSITORY
ENTITY1
ENTITY2
Android-MVP: model public interface GetWeatherInMoscowInteractor { Observable<WeatherResponse> get(); } public class GetWeatherInMoscowUseCase implements GetWeatherInMoscowInteractor { private final WeatherRepository repository; public GetWeatherInMoscowUseCase(WeatherRepository repo) { repository = repo; } @Override public Observable<WeatherResponse> get() { return repository.getWeather("Moscow"); } }
INTERACTOR
18
Android-MVP. Практика: Model public interface GetWeatherInMoscowInteractor { Observable<WeatherResponse> get(); } public class GetWeatherInMoscowUseCase implements GetWeatherInMoscowInteractor { private final WeatherRepository repository; private final CacheManager cacheManager; public GetWeatherInMoscowUseCase(WeatherRepository repo, CacheManager cacheMan) { repository = repo; cacheManager = cacheMan; } @Override public Observable<WeatherResponse> get() { return repository .getWeather("Moscow") .doOnNext(weather -> {cacheManager.put(weather);}); } }
19 INTERACTOR
Android-MVP: presenter public abstract class Presenter<V> { private volatile V view; public void attachView(V v) { view = v; } public void detachView() { view = null; } public void onCreate(Bundle arguments, Bundle savedInstanceState) { } public void onSaveInstanceState(Bundle bundle) { } public void onDestroy() { } }
PRESENTER
VIEWMODEL
20
public class WeatherPresenterImpl extends WeatherPresenter { private GetWeatherInMoscowInteractor getWeather; private WeatherMapper mapper; @Override public void loadWeather() { getWeather.get() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(weather -> updateUi(mapper.map(weather)),
throwable -> showError(WeatherError.GENERAL))); } } public abstract class WeatherPresenter extends Presenter<WeatherView> { public abstract void loadWeather(); }
Android-MVP: presenter
21
PRESENTER
VIEWMODEL
Android-MVP: view public interface WeatherView extends LCEView<WeatherViewModel,WeatherError> { enum WeatherError { GENERAL } } public interface LCEView<D, E> { void showLoading(); void hideLoading(); void setData(D data); void showContent(); void showError(E error); } public class WeatherViewModel implements Parcelable { int temperature; }
VIEW
22
Android-MVP: view public class WeatherFragment extends Fragment implements WeatherView { @Override public void showLoading() { // показать индикатор загрузки } ... }
23
VIEW
Android-MVP: view public class WeatherFragment extends Fragment implements WeatherView { WeatherPresenter presenter = new WeatherPresenterImpl(new GetWeatherInMoscowUseCase( new WeatherRetrofitRepository(RetrofitHelper.getWeatherApi())), new WeatherMapperImpl()); @Override public void showLoading() { // показать индикатор загрузки } ... }
24
VIEW
Android-MVP: view public class WeatherFragment
extends ComponentManagerFragment<WeatherComponent, WeatherView> implements WeatherView {
@Override protected WeatherComponent createComponent() { return DaggerWeatherComponent .builder() .appComponent(...) .build(); } @Override public void onViewCreated(final View view, final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); getPresenter().loadWeather(); }
... }
25
VIEW
Android-MVP: реальность public class WeatherFragment
extends ComponentManagerFragment<WeatherComponent, WeatherView> implements WeatherView {
... @Override public void onViewCreated(final View view, final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); getPresenter().loadWeather(); } ... }
27
public class WeatherPresenterImpl extends WeatherPresenter { @Override public void loadWeather() { getWeather.get() .subscribe(weather -> {
if (view != null) { updateUi(mMapper.map(weather)); } }, throwable -> { if (view != null) { showError(WeatherView.WeatherError.GENERAL); } }));
} }
Android-MVP: реальность (2)
32
Слой между View и Presenter: - (Со стороны Presenter) это View без жизненного цикла (~POJO)
- (Со стороны View) это Presenter с возможностью восстановить состояние у
View при необходимости
Android-MVP: о взаимодействии V и P
33
public class WeatherPresenterImpl extends WeatherPresenter { } public class WeatherFragment implements WeatherView { } public class WeatherCommunicationBus extends WeatherPresenter implements WeatherView { // view @Override public void showLoading() { if (view != null) { view.showLoading(); } } ... // presenter @Override public void loadWeather() { presenter.loadWeather(); } ... }
Android-MVP: о взаимодействии V и P
34
public class WeatherCommunicationBus extends WeatherPresenter implements WeatherView { public WeatherCommunicationBus(WeatherPresenter presenter) { mPresenter = presenter; mPresenter.attachView(this); } ... @Override public void onDestroy() { mPresenter.detachView(); mPresenter.onDestroy(); } ... }
Android-MVP: о взаимодействии V и P
35
public class WeatherViewState implements ViewState<WeatherView>, Parcelable { private final static int STATE_UNINITIALIZED = -1; private final static int STATE_DEFAULT = 0; private final static int STATE_SHOW_LOADING = 1; private final static int STATE_SHOW_ERROR = 2; private int mCurrentState = 0; private WeatherView.WeatherError mError; private WeatherViewModel mModel; public void setStateShowLoading() { mCurrentState = STATE_SHOW_LOADING; } ... public void apply(WeatherView view) { switch (mCurrentState) { case STATE_SHOW_LOADING: view.showLoading(); break; ... }
Android-MVP: о взаимодействии V и P
36
public abstract class MvpLceViewStateFragment extends MvpLceFragment { ... @Override public void showContent() { super.showContent(); viewState.setStateShowContent(getData()); } @Override public void showError(Throwable e, boolean pullToRefresh) { super.showError(e, pullToRefresh); viewState.setStateShowError(e, pullToRefresh); } ... }
Android-MVP: о взаимодействии V и P
37
public class WeatherCommunicationBus extends WeatherPresenter implements WeatherView { private final WeatherPresenter presenter; private WeatherView view; private WeatherViewState viewState; public WeatherCommunicationBus(WeatherPresenter presenter) { presenter = presenter; viewState = new WeatherViewState(); presenter.attachView(this); } @Override public void showLoading() { viewState.setStateShowLoading(); if (view != null) { mView.showLoading(); } } @Override public void attachView(WeatherView view) { view = view; viewState.apply(view); } }
Android-MVP: о взаимодействии V и P
42
Итоги Android-MVP:
- Много кода
+ Код намного более понятный и гибкий
+ Качество приложения выше
+ Скорость разработки выше
+ UX лучше (если не теряется состояние)
47
Ресурсы GIT:
https://github.com/nbarishok/RxMvpAndroid
Medium: https://medium.com/@nbarishok/on-communication-between-v-and-p-in-android-mvp-16caf773e1a5#.6mhrjpkw4
Dagger 2 + custom scopes:
https://guides.codepath.com/android/Dependency-Injection-with-Dagger-2
http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
DI & Сохранение presenter’a:
http://blog.bradcampbell.nz/mvp-presenters-that-survive-configuration-changes-part-2/ 48