Share
Embed
Clean Architecture for Android
By Alexander Bobro - https://www.facebook.com/profile.php?id=100002522756075&fref=ts
Why do mobile applications need architecture? We will discuss different architectural approaches for building flexible and testable software. I will show why clean architecture is one the best solutions. In this context, we will discuss different ways to organize presentation layer of Android apps, reveal pros and cons of them. I will show how to implement MVP for your android application, what problems you may face and how to solve them, how to test resulting solution.
uaMobiTech
November 14, 2015
Transcript
31 slides
-
The Clean Architecture выбор структурных элементов и их интерфейсов, с
помощью которых составлена система, а также их поведения в рамках сотрудничества структурных элементов; соединение выбранных элементов структуры и поведения во всё более крупные системы; архитектурный стиль, который направляет всю организацию — все элементы, их интерфейсы, их сотрудничество и их соединение. Архитектура программного обеспечения (англ. software architecture) — совокупность важнейших решений об организации программной системы. Архитектура включает: -
The Clean Architecture • Сделать возможным/Ускорить/Удешевить достижение бизнес-цели. • Сделать
приложение легко расширяемым на случай, если это понадобится • Уменьшить количество ошибок. • Сделать код более понятным и читабельным • Увеличить тестируемость кода. • Облегчить поддержку проекта или его отдельных модулей (в том числе и при смене разработчиков) Зачем нужна архитектура? -
Архитектурные шаблоны программного обеспечения (англ. Software architectural patterns) - обобщенные,
переиспользуемые решения для часто встречающихся проблем в архитектуре программного обеспечения при определенном контексте. Архитектурные паттерны имеют более широкий масштаб относительно паттернов проектирования (design patterns). -
The Clean Architecture Framework & Drivers Interface Adapters Aplication Business
Rules Enterprise Business Rules -
© 2015 Cogniance Inc. All Rights Reserved. Proprietary and Confidential.
solution The Clean Architecture Требования к архитектуре • предсказуемость, понятность • тестируемость • независимость от библиотек и фреймверков • независимость от UI • независимость от базы данных • независимость от внешних источников данных -
Implementation layers PRESENTATION LAYER Model View Presenter DOMAIN LAYER Business
Objects Business Logic Interactor Implementations DATA LAYER Repository Implementations API Client Interfaces DAO Interfaces INTERACTOR INTERFACES REPOSITORY INTERFACES CLOUD DB MEMORY -
The Clean Architecture DOMAIN LAYER Business Objects Business Logic Interactor
Implementations INTERACTOR INTERFACES REPOSITORY INTERFACES -
Collection<T> read(Criteria criteria) throws DataSourceException ; Collection<T> write(Iterable<T> data) throws
DataSourceException ; Collection<T> delete(Criteria criteria) throws DataSourceException ; Collection<T> deleteAll(Iterable<T> data) throws DataSourceException ; Repository<T> -
protected abstract Observable<T> provideUseCaseObservable(P params); public void execute(Subscriber<T> useCaseSubscriber, P
params) { this.subscription = this.provideUseCaseObservable(params) .subscribeOn(subscribeOnScheduler) .observeOn(observeOnScheduler) .subscribe(useCaseSubscriber); } public void unsubscribe() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } UseCase<T,P> -
private final RepositoryBase<Post> postRepository ; @Override protected Observable<List<Post>> provideUseCaseObservable (String
userId) { final Criteria criteria = new UserIdCriteria(userId) ; return Observable. create(new Observable.OnSubscribe<List<Post>>() { @Override public void call(Subscriber<? super List<Post>> subscriber) { try { subscriber.onNext(Stream. stream(postRepository .read(criteria)).toList()) ; } catch (DataSourceException e) { subscriber.onError(e) ; } finally { subscriber.onCompleted() ; } } }); } GetPostsByUserIdUseCase -
The Clean Architecture DATA LAYER Repository Implementations API Client Interfaces
DAO Interfaces CLOUD DB MEMORY -
ParseClientReaderSdk<T, P extends ParseObject> Class<P> parseClass; ObjectConverter< T, P> objectConverter
; ExceptionConverter exceptionConverter ; @Override public Collection<T> get(Criteria criteria) throws DataLayerException { ParseQuery<P> parseQuery = ParseQueryFactory. fromCriteria(parseClass, criteria); try { List<P> parseObjects = parseQuery.find() ; return Stream.stream(parseObjects).map( objectConverter ::convert).toList() ; } catch (ParseException e) { throw exceptionConverter .convert(e); } } -
@Override public Collection<Post> read(Criteria criteria) throws DataSourceException { if(isMemoryCacheValid(criteria)) {
return getDataFromMemoryCache(criteria) ; } else if(isDataBaseCacheValid(criteria)) { return getDataFromDataBaseCache(criteria) ; } else { return getDataFromServer(criteria) ; } } public Collection<Post> getDataFromServer (Criteria criteria) throws DataSourceException { try { Collection<Post> posts = postClient.get(criteria) ; return cachePosts(posts) ; } catch (RESTException e) { throw dataSourceExceptionConverter .convert(e) ; } } PostsRepository -
MVP. Базовая схема PRESENTER MODEL VIEW updates view user events
state change events call methods -
void initializeViewComponents (PlatformView platformView); void releaseViewComponents (); void setPresenter(P presenter);
ViewBase<P extends PresenterBase,PlatformView> -
@Override public void loadData() { view.showLoading(); dataUseCase.execute(new DataSubscriber(), getParams()); }
@Override public void onDataReceived(D data) { this.data = data; view.hideLoading(); updateViewState(); } protected void updateViewState() { view.displayData(domainToViewModelConverter.convert(data)); } DataPresenterBase -
Возникшие вопросы 1. что делать, если презентер "переживает" представление (опасность
утечки представления)? 2. что делать, если представление не может выполнить вызов презентера (например находится в неподходящем состоянии)? 3. что делать с параметрами, переданными в активити/фрагмент через интент или бандл? 4. что делать с жизненным циклом компонент андроида? 5. как сохранять состояние вью и презентеров? 6. что делать с компонентами зависящими от Context, Activity, Fragment? 7. что делать с навигацией? -
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); V viewBase
= getViewBase(); P presenterBase = getPresenterBase(); initView(viewBase, getView(), savedInstanceState); initPresenter(presenterBase, savedInstanceState); viewBase.setPresenter(presenterBase); presenterBase.attachToView(viewBase); } @Override public void onDestroyView() { super.onDestroyView(); getViewBase().releaseViewComponents(); getPresenterBase().detachFromView(); } FragmentBase<V,P> -
public interface UserPostsScreenNavigation { void navigateToUserDetailsScreen(User user); void navigateToPostDetailsScreen(Post post);
} UserPostsScreenNavigation navigation; @Override public void onUserSelected(User user) { navigation.navigateToUserDetailsScreen(user); } @Override public void onPostSelected(Post post) { navigation.navigateToPostDetailsScreen(post); } Navigation -
MVP. Расширенная схема PRESENTER NAVIGATOR VIEW APPLICATION MODEL state change
events call methods initializes presenter makes call during lifecycle navigation events updated views user events call application methods to switch views -
The Clean Architecture PRESENTER NAVIGATOR VIEW APPLICATION INTERACTOR state change
events call methods initializes presenter makes call during lifecycle navigation events updated views user events DATASOURCE ENTITIES manipulates entities state change events manipulates data call application methods to switch views