Доклад о подходе к разработке Android-приложений с использованием MVP и Clean Architecture. Никита рассмотрит преимущества этого подхода перед традиционным, уделит отдельное внимание вопросам сохранения состояния в Android-MVP, а также особенностям взаимодействия между V и P.
5. Почему MVP?
Важные характеристики системы:
- понятность кода
- расширяемость
- готовность к изменениям
- тестируемость
Это всё НЕ о традиционном подходе!
5
6. Почему 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
7. Почему 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
10. Android-MVP: описание MVP
VIEW PRESENTER MODEL
оповестить о
событии
запросить
данные
обновить UI получить данные
View максимально прост и пассивен
Model включает в себя слой получения данных и бизнес-логики
Presenter получает данные из Model, трансформирует их, отдает во View; решает, что делает
View.
10
11. Android-MVP: MVP + Clean architecture
VIEW PRESENTER INTERACTOR REPOSITORY
ENTITY1
ENTITY2
VIEWMODEL
V P M
DOMAIN LAYER DATA LAYER
11
13. Android-MVP: model
public interface Api {
@GET("weather")
Observable<WeatherResponse> get(@Query("APPID") String key, @Query("q") String q);
}
REPOSITORY
ENTITY1
ENTITY2
13
14. 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
15. 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
16. Android-MVP: model
public interface WeatherRepository {
Observable<WeatherResponse> getWeather(String city);
}
16
REPOSITORY
ENTITY1
ENTITY2
17. 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
18. 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
19. 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
20. 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
21. 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
22. 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
23. Android-MVP: view
public class WeatherFragment extends Fragment implements WeatherView {
@Override
public void showLoading() {
// показать индикатор загрузки
}
...
}
23
VIEW
24. 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
32. 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
33. Слой между View и Presenter:
- (Со стороны Presenter) это View без жизненного цикла (~POJO)
- (Со стороны View) это Presenter с возможностью восстановить состояние у
View при необходимости
Android-MVP: о взаимодействии V и P
33
34. 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
35. 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
36. 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
37. 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
47. Итоги
Android-MVP:
- Много кода
+ Код намного более понятный и гибкий
+ Качество приложения выше
+ Скорость разработки выше
+ UX лучше (если не теряется состояние)
47