Share
Embed
Clean architecture on Android
Overview and implementation examples (code snippets) for clean architecture implementation on Android.
Talk @ Droidcon Zagreb 2016
Marko Milos
April 28, 2016
Transcript
160 slides
-
Clean architecture • Independent of frameworks • Testable • Independent
of UI • Independent of database / storage Undabot -
Clean architecture • Independent of frameworks • Testable • Independent
of UI • Independent of database / storage • Independent of any external agency Undabot -
Layers Entities UseCase Abstraction Presenter Repository UI Data source UseCase
/ Interactors Entities Abstraction DOMAIN -
Layers Entities UseCase Abstraction Presenter Repository UI Data source UseCase
/ Interactors Entities Abstraction DOMAIN Repository Data source DATA -
Layers Entities UseCase Abstraction Presenter Repository UI Data source UI
Presenter PRESENTATION UseCase / Interactors Entities Abstraction DOMAIN Repository Data source DATA -
Presentation Model View Presenter (MVP) View • bind data to
view • scheduling animations / transitions • propagate user input to presenter • Activity, Fragment, View Undabot -
Presentation • orchestrate and execute use cases • prepare /
format data for the view Model View Presenter (MVP) View • bind data to view • scheduling animations / transitions • propagate user input to presenter • Activity, Fragment, View Presenter Undabot -
Flow View Presenter D O M A I N bind(v)
execute(s) Undabot -
Flow View Presenter D O M A I N bind(v)
execute(s) data Undabot -
Flow View Presenter D O M A I N bind(v)
execute(s) data process format Undabot -
Flow View Presenter D O M A I N bind(v)
execute(s) data process format display(d) Undabot -
Flow View Presenter D O M A I N execute(s)
data process format display(d) [user input] onInput() Undabot -
Abstraction public class Timeline { List<Tweet> tweets; } public interface
TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public class Timeline { List<Tweet> tweets; } Undabot -
Abstraction public class Timeline { List<Tweet> tweets; } public interface
TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public class Timeline { List<Tweet> tweets; } Undabot -
Abstraction public class Timeline { List<Tweet> tweets; } public interface
TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public class Timeline { List<Tweet> tweets; } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } Undabot -
Abstraction public class Timeline { List<Tweet> tweets; } public interface
TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelinePresenter extends Presenter<TimelineView> { void onRefresh(); void onTweetSelected(Tweet tweet); } public class Timeline { List<Tweet> tweets; } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } Undabot -
Abstraction public class Timeline { List<Tweet> tweets; } public interface
TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelinePresenter extends Presenter<TimelineView> { void onRefresh(); void onTweetSelected(Tweet tweet); } public interface TimelinePresenter extends Presenter<TimelineView> { void onRefresh(); void onTweetSelected(Tweet tweet); } public class Timeline { List<Tweet> tweets; } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } Undabot -
Abstraction public class Timeline { List<Tweet> tweets; } public interface
TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelinePresenter extends Presenter<TimelineView> { void onRefresh(); void onTweetSelected(Tweet tweet); } public interface TimelinePresenter extends Presenter<TimelineView> { void onRefresh(); void onTweetSelected(Tweet tweet); } public interface TimelinePresenter extends Presenter<TimelineView> { void onRefresh(); void onTweetSelected(Tweet tweet); } public class Timeline { List<Tweet> tweets; } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } public interface TimelineView extends View { void displayError(String message); void displayTimeline(List<Tweet> tweets); } Undabot -
View implementation public class TimelineActivity extends AppCompatActivity implements TimelineView {
@Inject TimelinePresenter timelinePresenter; SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectDependencies(); timelinePresenter.bind(this); swipeRefreshLayout.setOnRefreshListener(() -> timelinePresenter.onRefresh()); } @Override public void displayError(String message) { /* display error */ } @Override public void displayTimeline(Timeline timeline) { /* setup recycler, adapter etc */ } } Undabot -
View implementation public class TimelineActivity extends AppCompatActivity implements TimelineView {
@Inject TimelinePresenter timelinePresenter; SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectDependencies(); timelinePresenter.bind(this); swipeRefreshLayout.setOnRefreshListener(() -> timelinePresenter.onRefresh()); } @Override public void displayError(String message) { /* display error */ } @Override public void displayTimeline(Timeline timeline) { /* setup recycler, adapter etc */ } } Undabot -
View implementation public class TimelineActivity extends AppCompatActivity implements TimelineView {
@Inject TimelinePresenter timelinePresenter; SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectDependencies(); timelinePresenter.bind(this); swipeRefreshLayout.setOnRefreshListener(() -> timelinePresenter.onRefresh()); } @Override public void displayError(String message) { /* display error */ } @Override public void displayTimeline(Timeline timeline) { /* setup recycler, adapter etc */ } } public class TimelineActivity extends AppCompatActivity implements TimelineView { @Inject TimelinePresenter timelinePresenter; SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectDependencies(); timelinePresenter.bind(this); swipeRefreshLayout.setOnRefreshListener(() -> timelinePresenter.onRefresh()); } @Override public void displayError(String message) { /* display error */ } @Override public void displayTimeline(Timeline timeline) { /* setup recycler, adapter etc */ } } Undabot -
View implementation public class TimelineActivity extends AppCompatActivity implements TimelineView {
@Inject TimelinePresenter timelinePresenter; SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectDependencies(); timelinePresenter.bind(this); swipeRefreshLayout.setOnRefreshListener(() -> timelinePresenter.onRefresh()); } @Override public void displayError(String message) { /* display error */ } @Override public void displayTimeline(Timeline timeline) { /* setup recycler, adapter etc */ } } public class TimelineActivity extends AppCompatActivity implements TimelineView { @Inject TimelinePresenter timelinePresenter; SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectDependencies(); timelinePresenter.bind(this); swipeRefreshLayout.setOnRefreshListener(() -> timelinePresenter.onRefresh()); } @Override public void displayError(String message) { /* display error */ } @Override public void displayTimeline(Timeline timeline) { /* setup recycler, adapter etc */ } } public class TimelineActivity extends AppCompatActivity implements TimelineView { @Inject TimelinePresenter timelinePresenter; SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectDependencies(); timelinePresenter.bind(this); swipeRefreshLayout.setOnRefreshListener(() -> timelinePresenter.onRefresh()); } @Override public void displayError(String message) { /* display error */ } @Override public void displayTimeline(Timeline timeline) { /* setup recycler, adapter etc */ } } Undabot -
public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter { private
final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } Presenter implementation Undabot -
Presenter implementation public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter {
private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } Undabot -
Presenter implementation public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter {
private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter { private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } Undabot -
Presenter implementation public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter {
private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter { private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter { private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } Undabot -
Presenter implementation public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter {
private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter { private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter { private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } public class TimelinePresenterImpl extends AbsPresenter<TimelineView> implements TimelinePresenter { private final Navigator navigator; private final GetTimelineUseCase useCase; @Inject public TimelinePresenterImpl(Navigator navigator, GetTimelineUseCase useCase) { this.navigator = navigator; this.useCase = useCase; } @Override protected void onBind() { useCase.execute(new TimelineSubscriber()); } @Override public void onRefresh() { useCase.execute(new TimelineSubscriber()); } @Override public void onTweetSelected(Tweet tweet) { navigator.openTweetDetails(tweet); } } Undabot -
Callback / Subscriber private final class TimelineSubscriber extends Subscriber<Timeline> {
@Override public void onCompleted() { } @Override public void onError(Throwable e) { view().displayError(ErrorMessageFactory.create(e)); } @Override public void onNext(Timeline timeline) { view().displayTimeline(timeline.tweets); } } Undabot -
Callback / Subscriber private final class TimelineSubscriber extends Subscriber<Timeline> {
@Override public void onCompleted() { } @Override public void onError(Throwable e) { view().displayError(ErrorMessageFactory.create(e)); } @Override public void onNext(Timeline timeline) { view().displayTimeline(timeline.tweets); } } Undabot -
Callback / Subscriber private final class TimelineSubscriber extends Subscriber<Timeline> {
@Override public void onCompleted() { } @Override public void onError(Throwable e) { view().displayError(ErrorMessageFactory.create(e)); } @Override public void onNext(Timeline timeline) { view().displayTimeline(timeline.tweets); } } private final class TimelineSubscriber extends Subscriber<Timeline> { @Override public void onCompleted() { } @Override public void onError(Throwable e) { view().displayError(ErrorMessageFactory.create(e)); } @Override public void onNext(Timeline timeline) { view().displayTimeline(timeline.tweets); } } Undabot -
P R E S E N T A T I
O N Domain D A T A Undabot -
P R E S E N T A T I
O N Domain D A T A UseCase execute(s) Undabot -
P R E S E N T A T I
O N Domain D A T A UseCase Model execute(s) Undabot -
P R E S E N T A T I
O N Domain D A T A UseCase Abstraction Abstraction Abstraction Model execute(s) Undabot -
P R E S E N T A T I
O N Domain D A T A UseCase Abstraction Abstraction Abstraction Model execute(s) Undabot -
P R E S E N T A T I
O N Domain D A T A UseCase Abstraction Abstraction Abstraction Model Business logic execute(s) Undabot -
P R E S E N T A T I
O N Domain D A T A UseCase Abstraction Abstraction Abstraction Model Business logic execute(s) s.onNext(d) s.onError(t) Undabot -
Model UseCase Abstraction Abstraction Abstraction Model Business logic public class
Tweet { private String author; private String content; public Tweet(String author, String content) { this.author = author; this.content = content; } } public class Timeline { private List<Tweet> tweets; public Timeline(List<Tweet> tweets) { this.tweets = tweets; } } public class Location { private float lat; private float lng; public Location(float lat, float lng) { this.lat = lat; this.lng = lng; } } public class Region { private Bounds bounds; private Location location; private Map<Integer, Area> areas; public Region(Bounds bounds, Location location) { this.bounds = bounds; this.location = location; } } Undabot -
public interface Interactor<T> { void execute(Callback<T> callback); } Interactor UseCase
Abstraction Abstraction Abstraction Model Business logic Undabot -
Interactor UseCase Abstraction Abstraction Abstraction Model Business logic public interface
Interactor<T> { void execute(Subscriber<T> subscriber); } Undabot -
Interactor UseCase Abstraction Abstraction Abstraction Model Business logic public interface
Interactor<T> { void execute(Subscriber<T> subscriber); Observable<T> execute(); } Undabot -
Contract UseCase Abstraction Abstraction Abstraction Model Business logic abstract class
UseCase<T> implements Interactor<T> { private final ThreadExecutor threadExecutor; private final PostExecutionThread postExecutionThread; private Subscription subscription = Subscriptions.empty(); public UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { this.threadExecutor = threadExecutor; this.postExecutionThread = postExecutionThread; } abstract Observable<T> createObservable(); @Override public void execute(Subscriber<T> subscriber) { this.subscription = createObservable() .subscribeOn(Schedulers.from(threadExecutor)) .observeOn(postExecutionThread.getScheduler()) .subscribe(subscriber); } @Override public Observable<T> execute() { return createObservable(); } public void unsubscribe() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } } Undabot -
Contract UseCase Abstraction Abstraction Abstraction Model Business logic abstract class
UseCase<T> implements Interactor<T> { private final ThreadExecutor threadExecutor; private final PostExecutionThread postExecutionThread; private Subscription subscription = Subscriptions.empty(); public UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { this.threadExecutor = threadExecutor; this.postExecutionThread = postExecutionThread; } abstract Observable<T> createObservable(); @Override public void execute(Subscriber<T> subscriber) { this.subscription = createObservable() .subscribeOn(Schedulers.from(threadExecutor)) .observeOn(postExecutionThread.getScheduler()) .subscribe(subscriber); } @Override public Observable<T> execute() { return createObservable(); } public void unsubscribe() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } } Undabot -
Contract UseCase Abstraction Abstraction Abstraction Model Business logic abstract class
UseCase<T> implements Interactor<T> { private final ThreadExecutor threadExecutor; private final PostExecutionThread postExecutionThread; private Subscription subscription = Subscriptions.empty(); public UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { this.threadExecutor = threadExecutor; this.postExecutionThread = postExecutionThread; } abstract Observable<T> createObservable(); @Override public void execute(Subscriber<T> subscriber) { this.subscription = createObservable() .subscribeOn(Schedulers.from(threadExecutor)) .observeOn(postExecutionThread.getScheduler()) .subscribe(subscriber); } @Override public Observable<T> execute() { return createObservable(); } public void unsubscribe() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } } Undabot -
Contract UseCase Abstraction Abstraction Abstraction Model Business logic abstract class
UseCase<T> implements Interactor<T> { private final ThreadExecutor threadExecutor; private final PostExecutionThread postExecutionThread; private Subscription subscription = Subscriptions.empty(); public UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { this.threadExecutor = threadExecutor; this.postExecutionThread = postExecutionThread; } abstract Observable<T> createObservable(); @Override public void execute(Subscriber<T> subscriber) { this.subscription = createObservable() .subscribeOn(Schedulers.from(threadExecutor)) .observeOn(postExecutionThread.getScheduler()) .subscribe(subscriber); } @Override public Observable<T> execute() { return createObservable(); } public void unsubscribe() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } } Undabot -
Implementation UseCase Abstraction Abstraction Abstraction Model Business logic public class
GetTimelineUseCase extends UseCase<Timeline> { private final TimelineRepository timelineRepository; @Inject public GetTimelineUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, TimelineRepository timelineRepository) { super(threadExecutor, postExecutionThread); this.timelineRepository = timelineRepository; } @Override Observable<Timeline> createObservable() { return timelineRepository.getTimeline(); } } Undabot -
Implementation UseCase Abstraction Abstraction Abstraction Model Business logic public class
GetTimelineUseCase extends UseCase<Timeline> { private final TimelineRepository timelineRepository; @Inject public GetTimelineUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, TimelineRepository timelineRepository) { super(threadExecutor, postExecutionThread); this.timelineRepository = timelineRepository; } @Override Observable<Timeline> createObservable() { return timelineRepository.getTimeline(); } } Undabot -
Implementation UseCase Abstraction Abstraction Abstraction Model Business logic public class
GetTimelineUseCase extends UseCase<Timeline> { private final TimelineRepository timelineRepository; @Inject public GetTimelineUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, TimelineRepository timelineRepository) { super(threadExecutor, postExecutionThread); this.timelineRepository = timelineRepository; } @Override Observable<Timeline> createObservable() { return timelineRepository.getTimeline(); } } Undabot -
Abstraction UseCase Abstraction Abstraction Abstraction Model Business logic public interface
TimelineRepository { Observable<Timeline> getTimeline(); Observable<Timeline> getTimeline(String hashTag); Observable<Timeline> getTimeline(Region region); } Undabot -
Business logic UseCase Abstraction Abstraction Abstraction Model Business logic public
final class LocationOperations { /* Initialization */ public Region toRegion(Location location) { /* Business logic implementation */ } } Undabot -
Regional timeline feature get current location location available ? get
default location no yes -
Regional timeline feature calculate region get current location location available
? get default location no yes -
Regional timeline feature get timeline for region calculate region get
current location location available ? get default location no yes -
Regional timeline feature get timeline for region calculate region get
current location location available ? get default location no yes GetLocationUseCase -
Regional timeline feature get timeline for region calculate region get
current location location available ? get default location no yes GetLocationUseCase Business logic -
Regional timeline feature get timeline for region calculate region get
current location location available ? get default location no yes GetLocationUseCase Business logic GetRegionalTimelineUseCase -
Location get current location location available ? get default location
no yes public class GetLocationUseCase extends UseCase<Location> { private final LocationProvider locationProvider; @Inject public GetLocationUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, LocationProvider locationProvider) { super(threadExecutor, postExecutionThread); this.locationProvider = locationProvider; } @Override Observable<Location> createObservable() { return Observable .concat(locationProvider.getCurrentLocation(), locationProvider.getDefaultLocation()) .first(location -> location != null); } } Undabot -
Location get current location location available ? get default location
no yes public class GetLocationUseCase extends UseCase<Location> { private final LocationProvider locationProvider; @Inject public GetLocationUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, LocationProvider locationProvider) { super(threadExecutor, postExecutionThread); this.locationProvider = locationProvider; } @Override Observable<Location> createObservable() { return Observable .concat(locationProvider.getCurrentLocation(), locationProvider.getDefaultLocation()) .first(location -> location != null); } } Undabot -
Location get current location location available ? get default location
no yes public class GetLocationUseCase extends UseCase<Location> { private final LocationProvider locationProvider; @Inject public GetLocationUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, LocationProvider locationProvider) { super(threadExecutor, postExecutionThread); this.locationProvider = locationProvider; } @Override Observable<Location> createObservable() { return Observable .concat(locationProvider.getCurrentLocation(), locationProvider.getDefaultLocation()) .first(location -> location != null); } } Undabot -
Location get current location location available ? get default location
no yes public class GetLocationUseCase extends UseCase<Location> { private final LocationProvider locationProvider; @Inject public GetLocationUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, LocationProvider locationProvider) { super(threadExecutor, postExecutionThread); this.locationProvider = locationProvider; } @Override Observable<Location> createObservable() { return Observable .concat(locationProvider.getCurrentLocation(), locationProvider.getDefaultLocation()) .first(location -> location != null); } } public interface LocationProvider { Observable<Location> getCurrentLocation(); Observable<Location> getDefaultLocation(); } Undabot -
Regional timeline feature get timeline for region calculate region get
current location location available ? get default location no yes GetLocationUseCase Business logic GetRegionalTimelineUseCase -
Regional timeline use case public class GetRegionalTimelineUseCase extends UseCase<Timeline> {
private final GetLocationUseCase locationUseCase; private final LocationOperations locationOperations; private final TimelineRepository timelineRepository; @Inject public GetRegionalTimelineUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, GetLocationUseCase locationUseCase, LocationOperations locationOperations, TimelineRepository timelineRepository) { super(threadExecutor, postExecutionThread); this.locationUseCase = locationUseCase; this.locationOperations = locationOperations; this.timelineRepository = timelineRepository; } } GetRegionalTimelineUseCase Undabot -
Regional timeline use case @Override Observable<Timeline> createObservable() { return locationUseCase
.execute() .map(new Func1<Location, Region>() { @Override public Region call(Location location) { return locationOperations.toRegion(location); } }) .flatMap(new Func1<Region, Observable<Timeline>>() { @Override public Observable<Timeline> call(Region region) { return timelineRepository.getTimeline(region); } }); } public class GetRegionalTimelineUseCase extends UseCase<Timeline> { private final GetLocationUseCase locationUseCase; private final LocationOperations locationOperations; private final TimelineRepository timelineRepository; @Inject public GetRegionalTimelineUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, GetLocationUseCase locationUseCase, LocationOperations locationOperations, TimelineRepository timelineRepository) { super(threadExecutor, postExecutionThread); this.locationUseCase = locationUseCase; this.locationOperations = locationOperations; this.timelineRepository = timelineRepository; } } GetRegionalTimelineUseCase Undabot -
Regional timeline use case @Override Observable<Timeline> createObservable() { return locationUseCase
.execute() .map(location -> locationOperations.toRegion(location)) .flatMap(region -> timelineRepository.getTimeline(region)); } public class GetRegionalTimelineUseCase extends UseCase<Timeline> { private final GetLocationUseCase locationUseCase; private final LocationOperations locationOperations; private final TimelineRepository timelineRepository; @Inject public GetRegionalTimelineUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, GetLocationUseCase locationUseCase, LocationOperations locationOperations, TimelineRepository timelineRepository) { super(threadExecutor, postExecutionThread); this.locationUseCase = locationUseCase; this.locationOperations = locationOperations; this.timelineRepository = timelineRepository; } } GetRegionalTimelineUseCase Undabot -
Regional timeline use case @Override Observable<Timeline> createObservable() { return locationUseCase
.execute() .map(locationOperations::toRegion) .flatMap(timelineRepository::getTimeline); } public class GetRegionalTimelineUseCase extends UseCase<Timeline> { private final GetLocationUseCase locationUseCase; private final LocationOperations locationOperations; private final TimelineRepository timelineRepository; @Inject public GetRegionalTimelineUseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, GetLocationUseCase locationUseCase, LocationOperations locationOperations, TimelineRepository timelineRepository) { super(threadExecutor, postExecutionThread); this.locationUseCase = locationUseCase; this.locationOperations = locationOperations; this.timelineRepository = timelineRepository; } } GetRegionalTimelineUseCase Undabot -
Data P R E S E N T A T
I O N D O M A I N Undabot -
Data P R E S E N T A T
I O N D O M A I N Repository Undabot -
Data P R E S E N T A T
I O N D O M A I N Repository Data source Undabot -
Data P R E S E N T A T
I O N D O M A I N Repository Data source Network Disk DB Memory Undabot -
Data P R E S E N T A T
I O N D O M A I N Cache ORM Cache Retrofit Repository Data source Network Disk DB Memory Undabot -
Data P R E S E N T A T
I O N D O M A I N Cache ORM Cache Retrofit Repository Data source Network Disk DB Memory Data source factory Undabot -
Data P R E S E N T A T
I O N D O M A I N Cache ORM Cache Retrofit Repository Data source Network Disk DB Memory Undabot -
Data source public interface TimelineDataSource { Observable<Timeline> getTimeline();
Observable<Timeline> getTimeline(String hashTag); Observable<Timeline> getTimeline(Region region); // Other } Repository Data source Network Disk DB Memory Undabot -
Network implementation public class TimeLineDataSourceNetwork implements TimeLineDataSource { private
final TwitterAPI twitterAPI; private final TimelineResponseMapper mapper; public TimeLineDataSourceNetwork(TwitterAPI twitterAPI, TimelineResponseMapper mapper) { this.twitterAPI = twitterAPI; this.mapper = mapper; } @Override public Observable<Timeline> getTimeline() { return twitterAPI .getTimeline() .retryWhen(new RetryWithDelay(3, 500)) .map(timelineResponseResult -> mapper.toTimeline(timelineResponseResult.response())); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
Network implementation public class TimeLineDataSourceNetwork implements TimeLineDataSource { private
final TwitterAPI twitterAPI; private final TimelineResponseMapper mapper; public TimeLineDataSourceNetwork(TwitterAPI twitterAPI, TimelineResponseMapper mapper) { this.twitterAPI = twitterAPI; this.mapper = mapper; } @Override public Observable<Timeline> getTimeline() { return twitterAPI .getTimeline() .retryWhen(new RetryWithDelay(3, 500)) .map(timelineResponseResult -> mapper.toTimeline(timelineResponseResult.response())); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
Network implementation public class TimeLineDataSourceNetwork implements TimeLineDataSource { private
final TwitterAPI twitterAPI; private final TimelineResponseMapper mapper; public TimeLineDataSourceNetwork(TwitterAPI twitterAPI, TimelineResponseMapper mapper) { this.twitterAPI = twitterAPI; this.mapper = mapper; } @Override public Observable<Timeline> getTimeline() { return twitterAPI .getTimeline() .retryWhen(new RetryWithDelay(3, 500)) .map(timelineResponseResult -> mapper.toTimeline(timelineResponseResult.response())); } /* Other methods/implementations */ } public class TimeLineDataSourceNetwork implements TimeLineDataSource { private final TwitterAPI twitterAPI; private final TimelineResponseMapper mapper; public TimeLineDataSourceNetwork(TwitterAPI twitterAPI, TimelineResponseMapper mapper) { this.twitterAPI = twitterAPI; this.mapper = mapper; } @Override public Observable<Timeline> getTimeline() { return twitterAPI .getTimeline() .retryWhen(new RetryWithDelay(3, 500)) .map(timelineResponseResult -> mapper.toTimeline(timelineResponseResult.response())); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
Network implementation public interface TwitterAPI { @GET("statuses/home_timeline") Observable<Response<TimelineResponse>> getTimeline();
/* Other APIs */ } public class TimeLineDataSourceNetwork implements TimeLineDataSource { private final TwitterAPI twitterAPI; private final TimelineResponseMapper mapper; public TimeLineDataSourceNetwork(TwitterAPI twitterAPI, TimelineResponseMapper mapper) { this.twitterAPI = twitterAPI; this.mapper = mapper; } @Override public Observable<Timeline> getTimeline() { return twitterAPI .getTimeline() .retryWhen(new RetryWithDelay(3, 500)) .map(timelineResponseResult -> mapper.toTimeline(timelineResponseResult.response())); } /* Other methods/implementations */ } public class TimeLineDataSourceNetwork implements TimeLineDataSource { private final TwitterAPI twitterAPI; private final TimelineResponseMapper mapper; public TimeLineDataSourceNetwork(TwitterAPI twitterAPI, TimelineResponseMapper mapper) { this.twitterAPI = twitterAPI; this.mapper = mapper; } @Override public Observable<Timeline> getTimeline() { return twitterAPI .getTimeline() .retryWhen(new RetryWithDelay(3, 500)) .map(timelineResponseResult -> mapper.toTimeline(timelineResponseResult.response())); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
public class TimelineRepositoryImpl implements TimelineRepository { private final TimeLineDataSourceNetwork
networkDataSource; private final TimeLineDataSourceDisk diskDataSource; private final TimeLineDataSourceMem memoryDataSource; @Inject public TimelineRepositoryImpl(TimeLineDataSourceNetwork networkDataSource, TimeLineDataSourceDisk diskDataSource, TimeLineDataSourceMem memoryDataSource) { this.networkDataSource = networkDataSource; this.diskDataSource = diskDataSource; this.memoryDataSource = memoryDataSource; } @Override public Observable<Timeline> getTimeline() { return Observable .concat(memory(), diskWithSave(), networkWithSave()) .first(timeline -> timeline != null); } private Observable<Timeline> memory() { return memoryDataSource.timeline(); } private Observable<Timeline> diskWithSave() { return diskDataSource .timeline() .doOnNext(memoryDataSource::persist); // Save data to memory } private Observable<Timeline> networkWithSave() { return networkDataSource.timeline() .doOnNext(timeline -> { diskDataSource.persist(timeline); // Save data to disk memoryDataSource.persist(timeline); // Save data to memory }); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
public class TimelineRepositoryImpl implements TimelineRepository { private final TimeLineDataSourceNetwork
networkDataSource; private final TimeLineDataSourceDisk diskDataSource; private final TimeLineDataSourceMem memoryDataSource; @Inject public TimelineRepositoryImpl(TimeLineDataSourceNetwork networkDataSource, TimeLineDataSourceDisk diskDataSource, TimeLineDataSourceMem memoryDataSource) { this.networkDataSource = networkDataSource; this.diskDataSource = diskDataSource; this.memoryDataSource = memoryDataSource; } @Override public Observable<Timeline> getTimeline() { return Observable .concat(memory(), diskWithSave(), networkWithSave()) .first(timeline -> timeline != null); } private Observable<Timeline> memory() { return memoryDataSource.timeline(); } private Observable<Timeline> diskWithSave() { return diskDataSource .timeline() .doOnNext(memoryDataSource::persist); // Save data to memory } private Observable<Timeline> networkWithSave() { return networkDataSource.timeline() .doOnNext(timeline -> { diskDataSource.persist(timeline); // Save data to disk memoryDataSource.persist(timeline); // Save data to memory }); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
public class TimelineRepositoryImpl implements TimelineRepository { private final TimeLineDataSourceNetwork
networkDataSource; private final TimeLineDataSourceDisk diskDataSource; private final TimeLineDataSourceMem memoryDataSource; @Inject public TimelineRepositoryImpl(TimeLineDataSourceNetwork networkDataSource, TimeLineDataSourceDisk diskDataSource, TimeLineDataSourceMem memoryDataSource) { this.networkDataSource = networkDataSource; this.diskDataSource = diskDataSource; this.memoryDataSource = memoryDataSource; } @Override public Observable<Timeline> getTimeline() { return Observable .concat(memory(), diskWithSave(), networkWithSave()) .first(timeline -> timeline != null); } private Observable<Timeline> memory() { return memoryDataSource.timeline(); } private Observable<Timeline> diskWithSave() { return diskDataSource .timeline() .doOnNext(memoryDataSource::persist); // Save data to memory } private Observable<Timeline> networkWithSave() { return networkDataSource.timeline() .doOnNext(timeline -> { diskDataSource.persist(timeline); // Save data to disk memoryDataSource.persist(timeline); // Save data to memory }); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
public class TimelineRepositoryImpl implements TimelineRepository { private final TimeLineDataSourceNetwork
networkDataSource; private final TimeLineDataSourceDisk diskDataSource; private final TimeLineDataSourceMem memoryDataSource; @Inject public TimelineRepositoryImpl(TimeLineDataSourceNetwork networkDataSource, TimeLineDataSourceDisk diskDataSource, TimeLineDataSourceMem memoryDataSource) { this.networkDataSource = networkDataSource; this.diskDataSource = diskDataSource; this.memoryDataSource = memoryDataSource; } @Override public Observable<Timeline> getTimeline() { return Observable .concat(memory(), diskWithSave(), networkWithSave()) .first(timeline -> timeline != null); } private Observable<Timeline> memory() { return memoryDataSource.timeline(); } private Observable<Timeline> diskWithSave() { return diskDataSource .timeline() .doOnNext(memoryDataSource::persist); // Save data to memory } private Observable<Timeline> networkWithSave() { return networkDataSource.timeline() .doOnNext(timeline -> { diskDataSource.persist(timeline); // Save data to disk memoryDataSource.persist(timeline); // Save data to memory }); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
public class TimelineRepositoryImpl implements TimelineRepository { private final TimeLineDataSourceNetwork
networkDataSource; private final TimeLineDataSourceDisk diskDataSource; private final TimeLineDataSourceMem memoryDataSource; @Inject public TimelineRepositoryImpl(TimeLineDataSourceNetwork networkDataSource, TimeLineDataSourceDisk diskDataSource, TimeLineDataSourceMem memoryDataSource) { this.networkDataSource = networkDataSource; this.diskDataSource = diskDataSource; this.memoryDataSource = memoryDataSource; } @Override public Observable<Timeline> getTimeline() { return Observable .concat(memory(), diskWithSave(), networkWithSave()) .first(timeline -> timeline != null); } private Observable<Timeline> memory() { return memoryDataSource.timeline(); } private Observable<Timeline> diskWithSave() { return diskDataSource .timeline() .doOnNext(memoryDataSource::persist); // Save data to memory } private Observable<Timeline> networkWithSave() { return networkDataSource.timeline() .doOnNext(timeline -> { diskDataSource.persist(timeline); // Save data to disk memoryDataSource.persist(timeline); // Save data to memory }); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
public class TimelineRepositoryImpl implements TimelineRepository { private final TimeLineDataSourceNetwork
networkDataSource; private final TimeLineDataSourceDisk diskDataSource; private final TimeLineDataSourceMem memoryDataSource; @Inject public TimelineRepositoryImpl(TimeLineDataSourceNetwork networkDataSource, TimeLineDataSourceDisk diskDataSource, TimeLineDataSourceMem memoryDataSource) { this.networkDataSource = networkDataSource; this.diskDataSource = diskDataSource; this.memoryDataSource = memoryDataSource; } @Override public Observable<Timeline> getTimeline() { return Observable .concat(memory(), diskWithSave(), networkWithSave()) .first(timeline -> timeline != null); } private Observable<Timeline> memory() { return memoryDataSource.timeline(); } private Observable<Timeline> diskWithSave() { return diskDataSource .timeline() .doOnNext(memoryDataSource::persist); // Save data to memory } private Observable<Timeline> networkWithSave() { return networkDataSource.timeline() .doOnNext(timeline -> { diskDataSource.persist(timeline); // Save data to disk memoryDataSource.persist(timeline); // Save data to memory }); } /* Other methods/implementations */ } Repository Data source Network Disk DB Memory Undabot -
Data mapping public class Tweet { private long id;
private String author; private String content; private String createdAt; private boolean favourited; private int retweetCount; /* Constructors, getters, setters, methods... */ } Undabot -
Data mapping public class Tweet { private long id;
private String author; @SerializedName("text") private String content; @SerializedName(value = "created_at", alternate = {"created"}) private String createdAt; private boolean favourited; @SerializedName("retweet_count") private int retweetCount; public Tweet() { } /* Constructors, getters, setters, methods... */ } Undabot -
Data mapping @Table(database = TwitterDatabse.class) public class Tweet extends BaseModel
{ @PrimaryKey private long id; @Column private String author; @Column @SerializedName("text") private String content; @Column @SerializedName(value = "created_at", alternate = {"created"}) private String createdAt; @Column private boolean favourited; @Column @SerializedName("retweet_count") private int retweetCount; public Tweet() { } /* Constructors, getters, setters, methods... */ } Undabot -
Data mapping Tweet ApiTweet DbTweet Response model Response Mapper ApiMapper
DbMapper class TimelineResultMapper implements Func1<Result<TimelineResponse>, List<ApiTweet>> Undabot -
Data mapping Tweet ApiTweet DbTweet Response model Response Mapper ApiMapper
DbMapper class TimelineResultMapper implements Func1<Result<TimelineResponse>, List<ApiTweet>> .map(twitterResponseMapper); Undabot -
Flow View Presenter UseCase Abstraction Model Business logic Repository Data
source Network Disk DB Memory Undabot -
Flow View Presenter UseCase Abstraction Model Business logic Repository Data
source Network Disk DB Memory Undabot -
Flow View Presenter UseCase Abstraction Model Business logic Repository Data
source Network Disk DB Memory Undabot -
Flow View Presenter UseCase Abstraction Model Business logic Repository Data
source Network Disk DB Memory Undabot -
Flow View Presenter UseCase Abstraction Model Business logic Repository Data
source Network Disk DB Memory Undabot -
Flow View Presenter UseCase Abstraction Model Business logic Repository Data
source Network Disk DB Memory Undabot -
Flow View Presenter UseCase Abstraction Model Business logic Repository Data
source Network Disk DB Memory Undabot -
Swappable implementations View Presenter UseCase Abstraction Model Business logic Repository
Data source Location provider Image loader Glide Payment processor Pull to refresh Native time pickers Continuous location Production payment Network ORMLite Undabot -
Swappable implementations View Presenter UseCase Abstraction Model Business logic Repository
Data source Location provider Image loader Glide Payment processor Pull to refresh Swipe refresh Native time pickers Continuous location Production payment Network ORMLite Undabot -
Swappable implementations View Presenter UseCase Abstraction Model Business logic Repository
Data source Location provider Image loader Glide Payment processor Pull to refresh Swipe refresh Native time pickers Better Pickers Continuous location Production payment Network ORMLite Undabot -
Swappable implementations View Presenter UseCase Abstraction Model Business logic Repository
Data source Location provider Image loader Glide Payment processor Pull to refresh Swipe refresh Native time pickers Better Pickers Picasso Continuous location Production payment Network ORMLite Undabot -
Swappable implementations View Presenter UseCase Abstraction Model Business logic Repository
Data source Location provider Image loader Glide Payment processor Pull to refresh Swipe refresh Native time pickers Better Pickers Picasso Continuous location Fixed location Production payment Network ORMLite Undabot -
Swappable implementations View Presenter UseCase Abstraction Model Business logic Repository
Data source Location provider Image loader Glide Payment processor Pull to refresh Swipe refresh Native time pickers Better Pickers Picasso Continuous location Fixed location Production payment Network ORMLite DBFlow Undabot -
Swappable implementations View Presenter UseCase Abstraction Model Business logic Repository
Data source Location provider Image loader Glide Payment processor Pull to refresh Swipe refresh Native time pickers Better Pickers Picasso Continuous location Fixed location Production payment Fake payment Network ORMLite DBFlow Undabot -
Swappable implementations View Presenter UseCase Abstraction Model Business logic Repository
Data source Location provider Image loader Glide Payment processor Pull to refresh Swipe refresh Native time pickers Better Pickers Picasso Continuous location Fixed location Production payment Fake payment Network Local mock ORMLite DBFlow Undabot -
Packaging presentation feature A feature B feature C domain data
l a y e r s Package by layer Undabot -
Packaging p a c k a g e s presentation
feature A feature B feature C domain data l a y e r s Package by layer Undabot -
Packaging p a c k a g e s presentation
feature A feature B feature C domain data l a y e r s Package by layer feature A feature B feature C l a y e r s Package by feature Undabot -
Packaging p a c k a g e s packages
presentation feature A feature B feature C domain data l a y e r s Package by layer feature A feature B feature C l a y e r s Package by feature Undabot -
Package by layer data (module) └───repository └───cache └───database └───api └───exception
└───shared domain (module) └───model └───interactor └───executor └───abstraction └───shared presentation (module) └───presenter └───view └───navigation └───shared Undabot -
Package by layer data (module) └───repository └───cache └───database └───api └───exception
└───shared domain (module) └───model └───interactor └───executor └───abstraction └───shared presentation (module) └───presenter └───view └───navigation └───shared apply plugin: 'java' apply plugin: 'com.android.library' … compile project(':domain') apply plugin: ‘com.android.application' … compile project(':domain') compile project(':data') Undabot -
Package by layer data (module) └───repository └───cache └───database └───api └───exception
└───shared domain (module) └───model └───interactor └───executor └───abstraction └───shared presentation (module) └───presenter └───view └───navigation └───shared app (module) └───data └───repository └───cache └───database └───api └───exception └───shared └───domain └───model └───interactor └───executor └───abstraction └───shared └───presentation └───presenter └───view └───navigation └───shared Undabot -
Package by layer data (module) └───repository └───cache └───database └───api └───exception
└───shared domain (module) └───model └───interactor └───executor └───abstraction └───shared presentation (module) └───presenter └───view └───navigation └───shared app (module) └───data └───repository └───cache └───database └───api └───exception └───shared └───domain └───model └───interactor └───executor └───abstraction └───shared └───presentation └───presenter └───view └───navigation └───shared Undabot -
Package by feature app └───core └───data └───utils └───timeline └───details └───gallery
└───post └───profile └───notifications └───messaging └───metrics └───search └───settings Undabot -
Package by feature app └───core └───data └───utils └───timeline └───details └───gallery
└───post └───profile └───notifications └───messaging └───metrics └───search └───settings C C C C C C C C C C C profile gallery Undabot -
THANK YOU Q & A @the_mmarko twitter.com/ MarkoMilosMM google.com/+ Marko
Miloš, CTO @ Undabot