Slide 1

Slide 1 text

ROSIE: CLEAN USE CASE FRAMEWORK Jorge Juan Barroso Carmona [email protected] @flipper83 +JorgeJBarroso Pedro Gomez Sergio Gutierrez

Slide 2

Slide 2 text

Basho. Poet Karumi is the beauty of ordinary things spoken of in a simple way.

Slide 3

Slide 3 text

Sergio Gutierrez Senior Mobile Engineer Pedro Gomez Senior Mobile Engineer Alberto Gragera Technical Director Davide Mendolia Senior Full Stack Engineer

Slide 4

Slide 4 text

Adam Tornhill “If Clean Architecture is about running away from frameworks, does a clean architecture framework make sense?”

Slide 5

Slide 5 text

Adam Tornhill https://github.com/Karumi/Rosie

Slide 6

Slide 6 text

Why

Slide 7

Slide 7 text

We Start a New Project every three Months

Slide 8

Slide 8 text

Remove boilerplate code

Slide 9

Slide 9 text

Easy to change and evolve

Slide 10

Slide 10 text

Testable

Slide 11

Slide 11 text

Open Source

Slide 12

Slide 12 text

Activity Fragment Service View … Presenter View Use Case Rich Model Repository DataSource Api Retrofit SharedPref Api Sync Execution

Slide 13

Slide 13 text

UI Layer

Slide 14

Slide 14 text

divide ui framework from ui logic

Slide 15

Slide 15 text

improve UI Testing

Slide 16

Slide 16 text

Activity Fragment Service View … Presenter View Domain Model Presentation Model Use Case

Slide 17

Slide 17 text

Adam Tornhill public class SampleActivity extends RosieActivity implements SamplePresenter.View { @Inject @Presenter SamplePresenter presenter; @Override protected void onPreparePresenter() {/*...*/} }

Slide 18

Slide 18 text

Adam Tornhill public class SampleActivity extends RosieActivity implements SamplePresenter.View { @Inject @Presenter SamplePresenter presenter; @Override protected void onPreparePresenter() {/*...*/} }

Slide 19

Slide 19 text

Adam Tornhill public class SampleActivity extends RosieActivity implements SamplePresenter.View { @Inject @Presenter SamplePresenter presenter; @Override protected void onPreparePresenter() {/*...*/} }

Slide 20

Slide 20 text

Adam Tornhill public class SamplePresenter extends RosiePresenter { protected void initialize() {/*...*/} protected void update() {/*...*/} protected void pause() {/*...*/} protected void destroy() {/*...*/} private void sampleMethod() { View view = getView(); // Get the view linked to the presenter view.foo(); } public interface View extends RosiePresenter.View { void foo(); }

Slide 21

Slide 21 text

Adam Tornhill public class SamplePresenter extends RosiePresenter { protected void initialize() {/*...*/} protected void update() {/*...*/} protected void pause() {/*...*/} protected void destroy() {/*...*/} private void sampleMethod() { View view = getView(); // Get the view linked to the presenter view.foo(); } public interface View extends RosiePresenter.View { void foo(); }

Slide 22

Slide 22 text

Adam Tornhill public class SamplePresenter extends RosiePresenter { protected void initialize() {/*...*/} protected void update() {/*...*/} protected void pause() {/*...*/} protected void destroy() {/*...*/} private void sampleMethod() { View view = getView(); // Get the view linked to the presenter view.foo(); } public interface View extends RosiePresenter.View { void foo(); }

Slide 23

Slide 23 text

Adam Tornhill public class SamplePresenter extends RosiePresenter { protected void initialize() {/*...*/} protected void update() {/*...*/} protected void pause() {/*...*/} protected void destroy() {/*...*/} private void sampleMethod() { View view = getView(); // Get the view linked to the presenter view.foo(); } public interface View extends RosiePresenter.View { void foo(); }

Slide 24

Slide 24 text

we need extract common code

Slide 25

Slide 25 text

Adam Tornhill public final class PresenterLifeCycleLinker { public void initializeLifeCycle(Object source, RosiePresenter.View view) {
 if (source == null) {
 throw new IllegalArgumentException(
 "The source instance used to initialize the presenters can't be null");
 }
 if (view == null) {
 throw new IllegalArgumentException(
 "The view instance used to initialize the presenters can't be null");
 }
 
 addAnnotatedPresenter(source);
 setView(view);
 initializePresenters();
 }

Slide 26

Slide 26 text

Adam Tornhill public final class PresenterLifeCycleLinker { public void initializeLifeCycle(Object source, RosiePresenter.View view) {
 if (source == null) {
 throw new IllegalArgumentException(
 "The source instance used to initialize the presenters can't be null");
 }
 if (view == null) {
 throw new IllegalArgumentException(
 "The view instance used to initialize the presenters can't be null");
 }
 
 addAnnotatedPresenter(source);
 setView(view);
 initializePresenters();
 }

Slide 27

Slide 27 text

avoid NullPointers

Slide 28

Slide 28 text

Adam Tornhill void resetView() {
 final Class viewClass = getViewInterfaceClass();
 InvocationHandler emptyHandler = new InvocationHandler() {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 return null;
 }
 };
 ClassLoader classLoader = viewClass.getClassLoader();
 Class[] interfaces = new Class[1];
 interfaces[0] = viewClass;
 this.view = (T) Proxy.newProxyInstance(classLoader, interfaces, emptyHandler); }

Slide 29

Slide 29 text

Adam Tornhill void resetView() {
 final Class viewClass = getViewInterfaceClass();
 InvocationHandler emptyHandler = new InvocationHandler() {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 return null;
 }
 };
 ClassLoader classLoader = viewClass.getClassLoader();
 Class[] interfaces = new Class[1];
 interfaces[0] = viewClass;
 this.view = (T) Proxy.newProxyInstance(classLoader, interfaces, emptyHandler); }

Slide 30

Slide 30 text

Adam Tornhill void resetView() {
 final Class viewClass = getViewInterfaceClass();
 InvocationHandler emptyHandler = new InvocationHandler() {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 return null;
 }
 };
 ClassLoader classLoader = viewClass.getClassLoader();
 Class[] interfaces = new Class[1];
 interfaces[0] = viewClass;
 this.view = (T) Proxy.newProxyInstance(classLoader, interfaces, emptyHandler); }

Slide 31

Slide 31 text

Domain Layer

Slide 32

Slide 32 text

Use cases define operations over our software

Slide 33

Slide 33 text

Domain Model (Inmutable please) Domain Model Use Case Rich Model Domain Model

Slide 34

Slide 34 text

Adam Tornhill public class ComicsPresenter extends RosiePresenter { public void getComics() { createUseCaseCall(getComics) .args(superHeroId, authorId) .onSuccess(new OnSuccessCallback() { /*...*/ }) .onError(new OnErrorCallback() { /*...*/ }) .execute(); }

Slide 35

Slide 35 text

Adam Tornhill public class ComicsPresenter extends RosiePresenter { public void getComics() { createUseCaseCall(getComics) .args(superHeroId, authorId) .name(“sortByDate”) .onSuccess(new OnSuccessCallback() { /*...*/ }) .onError(new OnErrorCallback() { /*...*/ }) .execute(); }

Slide 36

Slide 36 text

Adam Tornhill public class ComicsPresenter extends RosiePresenter { public void getComics() { createUseCaseCall(getComics) .args(superHeroId, authorId) .name(“sortByDate”) .onSuccess(new OnSuccessCallback() { /*...*/ }) .onError(new OnErrorCallback() { /*...*/ }) .execute(); }

Slide 37

Slide 37 text

Adam Tornhill public class ComicsPresenter extends RosiePresenter { public void getComics() { createUseCaseCall(getComics) .args(superHeroId, authorId) .name(“sortByDate”) .onSuccess(new OnSuccessCallback() { /*...*/ }) .onError(new OnErrorCallback() { /*...*/ }) .execute(); }

Slide 38

Slide 38 text

Adam Tornhill public class GetComics extends RosieUseCase { @UseCase (name = “sortByDate”) public void getComicsSortedByDate(String superHeroId, String authorId) { try { Comics comics = getComics(superHeroId, authorId); notifySuccess(comics.getSortedByDate()); } catch (DataException e) { notifyError(generateDataError(e)); } } }

Slide 39

Slide 39 text

Adam Tornhill public class GetComics extends RosieUseCase { @UseCase (name = “sortByDate”) public void getComicsSortedByDate(String superHeroId, String authorId) { try { Comics comics = getComics(superHeroId, authorId); notifySuccess(comics.getSortedByDate()); } catch (DataException e) { notifyError(generateDataError(e)); } } }

Slide 40

Slide 40 text

Adam Tornhill public class GetComics extends RosieUseCase { @UseCase (name = “sortByDate”) public void getComicsSortedByDate(String superHeroId, String authorId) { try { Comics comics = getComics(superHeroId, authorId); notifySuccess(comics.getSortedByDate()); } catch (DataException e) { notifyError(generateDataError(e)); } } }

Slide 41

Slide 41 text

Adam Tornhill public class GetComics extends RosieUseCase { @UseCase (name = “sortByDate”) public void getComicsSortedByDate(String superHeroId, String authorId) { try { Comics comics = getComics(superHeroId, authorId); notifySuccess(comics.getSortedByDate()); } catch (DataException e) { notifyError(generateDataError(e)); } } }

Slide 42

Slide 42 text

Use cases are async

Slide 43

Slide 43 text

Adam Tornhill public class UseCaseHandler {
 
 private final TaskScheduler taskScheduler;
 private final ErrorHandler errorHandler;
 
 public UseCaseHandler(TaskScheduler taskScheduler, ErrorHandler errorHandler) {
 this.taskScheduler = taskScheduler;
 this.errorHandler = errorHandler;
 }

Slide 44

Slide 44 text

Adam Tornhill public class UseCaseHandler {
 
 private final TaskScheduler taskScheduler;
 private final ErrorHandler errorHandler;
 
 public UseCaseHandler(TaskScheduler taskScheduler, ErrorHandler errorHandler) {
 this.taskScheduler = taskScheduler;
 this.errorHandler = errorHandler;
 }

Slide 45

Slide 45 text

Adam Tornhill public class UseCaseHandler {
 
 private final TaskScheduler taskScheduler;
 private final ErrorHandler errorHandler;
 
 public UseCaseHandler(TaskScheduler taskScheduler, ErrorHandler errorHandler) {
 this.taskScheduler = taskScheduler;
 this.errorHandler = errorHandler;
 }

Slide 46

Slide 46 text

Adam Tornhill public interface TaskScheduler {
 void execute(UseCaseWrapper useCaseWrapper);
 } public class TaskSchedulerJobQueue implements TaskScheduler {
 private final JobManager jobManager;
 
 /**/
 
 @Override public void execute(UseCaseWrapper useCaseWrapper) {
 UseCaseWrapperJob useCaseWrapperJob = new UseCaseWrapperJob(useCaseWrapper);
 jobManager.addJobInBackground(useCaseWrapperJob);
 }
 }

Slide 47

Slide 47 text

Adam Tornhill public interface TaskScheduler {
 void execute(UseCaseWrapper useCaseWrapper);
 } public class TaskSchedulerJobQueue implements TaskScheduler {
 private final JobManager jobManager;
 
 /**/
 
 @Override public void execute(UseCaseWrapper useCaseWrapper) {
 UseCaseWrapperJob useCaseWrapperJob = new UseCaseWrapperJob(useCaseWrapper);
 jobManager.addJobInBackground(useCaseWrapperJob);
 }
 }

Slide 48

Slide 48 text

Adam Tornhill public interface TaskScheduler {
 void execute(UseCaseWrapper useCaseWrapper);
 } public class TaskSchedulerJobQueue implements TaskScheduler {
 private final JobManager jobManager;
 
 /**/
 
 @Override public void execute(UseCaseWrapper useCaseWrapper) {
 UseCaseWrapperJob useCaseWrapperJob = new UseCaseWrapperJob(useCaseWrapper);
 jobManager.addJobInBackground(useCaseWrapperJob);
 }
 }

Slide 49

Slide 49 text

avoid memory leaks

Slide 50

Slide 50 text

Adam Tornhill protected final UseCaseCall createUseCaseCall(RosieUseCase useCase) {
 UseCaseCall useCaseCall = new UseCaseCall(useCase, useCaseHandler);
 retainUseCaseCall(useCaseCall);
 return useCaseCall;
 }

Slide 51

Slide 51 text

Adam Tornhill protected final UseCaseCall createUseCaseCall(RosieUseCase useCase) {
 UseCaseCall useCaseCall = new UseCaseCall(useCase, useCaseHandler);
 retainUseCaseCall(useCaseCall);
 return useCaseCall;
 }

Slide 52

Slide 52 text

Adam Tornhill final class UseCaseParams {
 
 private final String useCaseName;
 private final Object[] args;
 private WeakReference onSuccessCallback;
 private WeakReference onErrorCallback; /**/ }

Slide 53

Slide 53 text

Adam Tornhill final class UseCaseParams {
 
 private final String useCaseName;
 private final Object[] args;
 private WeakReference onSuccessCallback;
 private WeakReference onErrorCallback; /**/ }

Slide 54

Slide 54 text

Data layer

Slide 55

Slide 55 text

Be agnostic from Data origin

Slide 56

Slide 56 text

Be agnostic from Data origin

Slide 57

Slide 57 text

Repository DataSource Api Retrofit Api Model Mapper Domain Model Cache level DataSource Realm Realm Realm Model Mapper Use case/ rich model Populator

Slide 58

Slide 58 text

Adam Tornhill public class SampleRepository extends RosieRepository { @Inject public SampleRepository(SampleApiDataSource sampleApiDataSource, SampleCacheDataSource sampleCacheDataSource) { addReadableDataSources(sampleApiDataSource); addCacheDataSources(sampleCacheDataSource); } }

Slide 59

Slide 59 text

Adam Tornhill public class SampleRepository extends RosieRepository { @Inject public SampleRepository(SampleApiDataSource sampleApiDataSource, SampleCacheDataSource sampleCacheDataSource) { addReadableDataSources(sampleApiDataSource); addCacheDataSources(sampleCacheDataSource); } }

Slide 60

Slide 60 text

Adam Tornhill public class SampleRepository extends RosieRepository { @Inject public SampleRepository(SampleApiDataSource sampleApiDataSource, SampleCacheDataSource sampleCacheDataSource) { addReadableDataSources(sampleApiDataSource); addCacheDataSources(sampleCacheDataSource); } }

Slide 61

Slide 61 text

Adam Tornhill public class SampleRepository extends RosieRepository { @Inject public SampleRepository() { PaginatedCacheDataSource inMemoryCacheDataSource = new InMemoryCacheDataSource<>(new TimeProvider(), MINUTES.toMillis(5)); addCacheDataSources(inMemoryCacheDataSource); } }

Slide 62

Slide 62 text

Adam Tornhill Key key = /*…*/; Value value; Collection values; RosieRepository repository = /*...*/; value = repository.getByKey(key); value = repository.getByKey(key, ReadPolicy.CACHE_ONLY); values = repository.getAll(); values = repository.getAll(ReadPolicy.READABLE_ONLY); repository.addOrUpdate(value); repository.addOrUpdate(value, WritePolicy.WRITE_ONCE); repository.deleteByKey(key); repository.deleteAll();

Slide 63

Slide 63 text

Adam Tornhill Key key = /*…*/; Value value; Collection values; RosieRepository repository = /*...*/; value = repository.getByKey(key); value = repository.getByKey(key, ReadPolicy.CACHE_ONLY); values = repository.getAll(); values = repository.getAll(ReadPolicy.READABLE_ONLY); repository.addOrUpdate(value); repository.addOrUpdate(value, WritePolicy.WRITE_ONCE); repository.deleteByKey(key); repository.deleteAll();

Slide 64

Slide 64 text

Adam Tornhill Key key = /*…*/; Value value; Collection values; RosieRepository repository = /*...*/; value = repository.getByKey(key); value = repository.getByKey(key, ReadPolicy.CACHE_ONLY); values = repository.getAll(); values = repository.getAll(ReadPolicy.READABLE_ONLY); repository.addOrUpdate(value); repository.addOrUpdate(value, WritePolicy.WRITE_ONCE); repository.deleteByKey(key); repository.deleteAll();

Slide 65

Slide 65 text

Adam Tornhill Key key = /*…*/; Value value; Collection values; RosieRepository repository = /*...*/; value = repository.getByKey(key); value = repository.getByKey(key, ReadPolicy.CACHE_ONLY); values = repository.getAll(); values = repository.getAll(ReadPolicy.READABLE_ONLY); repository.addOrUpdate(value); repository.addOrUpdate(value, WritePolicy.WRITE_ONCE); repository.deleteByKey(key); repository.deleteAll();

Slide 66

Slide 66 text

Repository DataSource Api Retrofit Api Model Mapper Domain Model Cache level DataSource Realm Realm Realm Model Mapper Use case/ rich model Populator

Slide 67

Slide 67 text

Adam Tornhill public class InMemoryCacheDataSource> implements CacheDataSource { protected final TimeProvider timeProvider; protected final long ttlInMillis; protected final Map items; protected long lastItemsUpdate; public InMemoryCacheDataSource(TimeProvider timeProvider, long ttlInMillis) { this.timeProvider = timeProvider; this.ttlInMillis = ttlInMillis; this.items = new LinkedHashMap<>(); } @Override public synchronized V getByKey(K key) { return items.get(key); } @Override public synchronized Collection getAll() { return new ArrayList<>(items.values()); }

Slide 68

Slide 68 text

Adam Tornhill public class InMemoryCacheDataSource> implements CacheDataSource { protected final TimeProvider timeProvider; protected final long ttlInMillis; protected final Map items; protected long lastItemsUpdate; public InMemoryCacheDataSource(TimeProvider timeProvider, long ttlInMillis) { this.timeProvider = timeProvider; this.ttlInMillis = ttlInMillis; this.items = new LinkedHashMap<>(); } @Override public synchronized V getByKey(K key) { return items.get(key); } @Override public synchronized Collection getAll() { return new ArrayList<>(items.values()); }

Slide 69

Slide 69 text

Adam Tornhill public class InMemoryCacheDataSource> implements CacheDataSource { protected final TimeProvider timeProvider; protected final long ttlInMillis; protected final Map items; protected long lastItemsUpdate; public InMemoryCacheDataSource(TimeProvider timeProvider, long ttlInMillis) { this.timeProvider = timeProvider; this.ttlInMillis = ttlInMillis; this.items = new LinkedHashMap<>(); } @Override public synchronized V getByKey(K key) { return items.get(key); } @Override public synchronized Collection getAll() { return new ArrayList<>(items.values()); }

Slide 70

Slide 70 text

works with Cache Levels and Populate Data

Slide 71

Slide 71 text

Adam Tornhill public Collection getAll(ReadPolicy policy) throws Exception {
 Collection values = null;
 
 if (policy.useCache()) {
 values = getValuesFromCaches();
 }
 
 if (values == null && policy.useReadable()) {
 values = getValuesFromReadables();
 }
 
 if (values != null) {
 populateCaches(values);
 }
 
 return values;
 }

Slide 72

Slide 72 text

Adam Tornhill public Collection getAll(ReadPolicy policy) throws Exception {
 Collection values = null;
 
 if (policy.useCache()) {
 values = getValuesFromCaches();
 }
 
 if (values == null && policy.useReadable()) {
 values = getValuesFromReadables();
 }
 
 if (values != null) {
 populateCaches(values);
 }
 
 return values;
 }

Slide 73

Slide 73 text

Adam Tornhill private Collection getValuesFromCaches() throws Exception {
 Collection values = null;
 
 for (CacheDataSource cacheDataSource : cacheDataSources) {
 values = cacheDataSource.getAll();
 
 if (values != null) {
 if (areValidValues(values, cacheDataSource)) {
 break;
 } else {
 cacheDataSource.deleteAll();
 values = null;
 }
 }
 }
 
 return values;
 }

Slide 74

Slide 74 text

Adam Tornhill private Collection getValuesFromCaches() throws Exception {
 Collection values = null;
 
 for (CacheDataSource cacheDataSource : cacheDataSources) {
 values = cacheDataSource.getAll();
 
 if (values != null) {
 if (areValidValues(values, cacheDataSource)) {
 break;
 } else {
 cacheDataSource.deleteAll();
 values = null;
 }
 }
 }
 
 return values;
 }

Slide 75

Slide 75 text

Adam Tornhill public Collection getAll(ReadPolicy policy) throws Exception {
 Collection values = null;
 
 if (policy.useCache()) {
 values = getValuesFromCaches();
 }
 
 if (values == null && policy.useReadable()) {
 values = getValuesFromReadables();
 }
 
 if (values != null) {
 populateCaches(values);
 }
 
 return values;
 }

Slide 76

Slide 76 text

Adam Tornhill public Collection getAll(ReadPolicy policy) throws Exception {
 Collection values = null;
 
 if (policy.useCache()) {
 values = getValuesFromCaches();
 }
 
 if (values == null && policy.useReadable()) {
 values = getValuesFromReadables();
 }
 
 if (values != null) {
 populateCaches(values);
 }
 
 return values;
 }

Slide 77

Slide 77 text

protected void populateCaches(Collection values) throws Exception {
 for (CacheDataSource cacheDataSource : cacheDataSources) {
 cacheDataSource.addOrUpdateAll(values);
 }
 }

Slide 78

Slide 78 text

Conclusions

Slide 79

Slide 79 text

This is not a Silver bullet

Slide 80

Slide 80 text

Easy to write New Features

Slide 81

Slide 81 text

Thought for new Projects

Slide 82

Slide 82 text

Forget Concurrency

Slide 83

Slide 83 text

Ready to write Test and Mock

Slide 84

Slide 84 text

It is open source. do you want to Contribute?

Slide 85

Slide 85 text

Find me I am very social! [email protected] @flipper83 +JorgeJBarroso Questions?

Slide 86

Slide 86 text

No content