$30 off During Our Annual Pro Sale. View Details »

Reactive Clean Architecture

Reactive Clean Architecture

Talk on how to think when building Android apps using a reactive Clean Architecture. This talk was held at the Stockholm Android Meetup group April 17 2018 at Developers bay.

Viktor Nyblom

April 17, 2018
Tweet

Other Decks in Technology

Transcript

  1. Key points ! Use Cases ! Dependencies ! Models !

    Reactive ! Testable ! Independent of frameworks
  2. What is a Use Case? ! Captures application business rules

    ! Independent of framework ! Shows WHAT the application does, not HOW X Interactor
  3. Presentation data flow View Presenter D O M A I

    N User interaction onRefresh() execute() domainData format() showData(data) Get
 Feed
 Interactor fun getFeed(): Single<List<Tweets>>
  4. What if we could observe a stream of data forever

    and react to new versions of that data?
  5. Presentation data flow View Presenter D O M A I

    N User interaction onRefresh() execute() domainData format() updateData(data) Refresh
 Feed
 Interactor Observe
 Feed
 Interactor Create
 Tweet
 Interactor execute() onCreateTweet()
  6. class TweetFeedPresenter @Inject constructor(
 private val refreshFeedInteractor: RefreshFeedInteractor,
 private val

    observeFeedInteractor: ObserveFeedInteractor,
 private val mapToDisplayableItem: Mapper<DisplayableItem>
 ) : TweetFeedContract.Presenter { private val disposables: CompositeDisposable
 ... } Presentation
  7. override fun bindToFeed(): Disposable =
 observeFeedInteractor
 .excecute()
 .onSubscribe { view?.showLoading()

    }
 .map(mapToDisplayableItem)
 .subscribe( 
 { tweets -> 
 if (tweets.notEmpty())
 view?.updateFeed(tweets)
 else
 view?.showEmpty()
 },
 { throwable -> loge(throwable) } )
 TweetFeedPresenter
  8. What the map? public class Item { private String validTo;


    private float price; private String validFrom; private int type; }
  9. What the map? public class Item { @SerializedName ("validto") private

    String validTo; @SerializedName ("price")
 private float price; @SerializedName ("validfrom") private String validFrom; @SerializedName (value = "type") private int type; }
  10. What the map? @DatabaseTable (name = “item")
 public class Item

    { @DatabaseField (generatedId = true) private int _id; @DatabaseField @SerializedName ("validto") private String validTo; @DatabaseField (canBeNull = false) @SerializedName ("price")
 private float price; @DatabaseField (canBeNull = false) @SerializedName ("validfrom") private String validFrom; @DatabaseField @SerializedName (value = "type") private int type; }
  11. What the map? public class APIItem { private int validto;


    private float price; private int validfrom; private int type; } @DatabaseTable (name = “item")
 public class DatabaseItem { @DatabaseField (generatedId = true) private int _id;
 @DatabaseField private int validTo;
 @DatabaseField
 private float price; @DatabaseField
 private int validFrom; @DatabaseField
 private int type; @DatabaseField
 private boolean dirty; }
  12. What the map? @DatabaseTable (name = “item")
 public class DatabaseItem

    { @DatabaseField (generatedId = true) private int _id;
 @DatabaseField private int validTo;
 @DatabaseField
 private float price; @DatabaseField
 private int validFrom; @DatabaseField
 private int type; @DatabaseField
 private boolean dirty; } 
 public class DisplayableItem { private int _id; private DateTime validTo; private String formattedTo;
 private String formattedPrice;
 private DateTime validFrom;
 private String formattedFrom;
 private ItemType type;
 private boolean selected; }
  13. What the map Data Presentation Domain Model Presenter View Interactor

    Model Operation API Repository Cache DB Abstraction
  14. What the map Data Presentation Domain Presentation
 Model Presenter View

    Interactor Domain
 Model Operation API Repository Cache DB Abstraction API
 Model Cache
 Model
  15. Domain data flow D A T A execute() domainData domainData

    Refresh
 Feed
 Interactor Abstraction
 : Tweet Repository P R E S E N TA T
 I
 O N Observe
 Feed
 Interactor onComplete() retrieveFeed() Abstraction
 : TweetList
 Repository onNext()
  16. Interactors abstract class CompletableInteractor<in Params>( private val executionScheduler: Scheduler, private

    val postExecutionScheduler: Scheduler) { abstract fun buildInteractorCompletable(params: Params): Completable fun execute(params: Params): Completable = buildInteractorObservable(params) .subscribeOn(executionScheduler) .observeOn(postExecutionScheduler) .subscribe() }
  17. Interactors class RefreshFeedInteractor @Inject constructor( private val tweetRepository: TweetRepository, executionScheduler:

    Scheduler, postExecutionScheduler: Scheduler) : CompletableInteractor<FeedSpec>(executionScheduler, postExecutionScheduler) { override fun buildInteractorCompletable(params: FeedSpec): Completable = tweetRepository.refreshFeed(params) }
  18. Interactors abstract class ObservableInteractor<T, in Params>( private val executionScheduler: Scheduler,

    private val postExecutionScheduler: Scheduler) { abstract fun buildInteractorCompletable(params: Params): Observable<T> fun execute(params: Params): Observable<T> = buildInteractorObservable(params) .subscribeOn(executionScheduler) .observeOn(postExecutionScheduler) .subscribe() }
  19. Interactors class RefreshFeedInteractor @Inject constructor( private val tweetListRepository: TweetListRepository, executionScheduler:

    Scheduler, postExecutionScheduler: Scheduler) : ObservableInteractor<List<Tweet>, FeedSpec>(executionScheduler, postExecutionScheduler) { override fun buildInteractorCompletable(params: FeedSpec): Observable<List<Tweet>> = tweetListRepository.observeFeed(params) } .filter { it.createdDate.after(someDate) } .compose { customTransformer }
  20. class ReactiveTwitterRepository( private val apiMapper: ApiToStoreMapper, 
 private val api:

    SomeApi, 
 private val store: ReactiveStore) : TweetRepository { override fun refreshFeed(): Completable =
 api.retrieveTweetList()
 .map { apiMapper }
 .doOnSuccess { store.storeTweets(it) }
 .toCompletable() ... }
  21. class ReactiveTwitterRepository( private val apiMapper: ApiToStoreMapper, 
 private val domainMapper:

    StoreToDomainMapper, 
 private val api: SomeApi, 
 private val store: ReactiveStore) : TweetRepository { ... override fun getFeedObservable(feedSpec: FeedSpec):
 Observable<List<Tweet> =
 store.getFeedObservable()
 .map(domainMapper) ... }
  22. interface ReactiveStore<Key, Value> { fun storeSingle(item: Value): Completable fun storeAll(items:

    List<Value>): Completable fun replaceAll(items: List<Value>): Completable fun getSingle(key: Key): Observable<Value> fun getAll(): Observable<List<Value>> } ReactiveStore
  23. ReactiveStore ! Coordinates putting data in persistence and/or cache !

    Create, store and feed the stream ! Implementation depends on the application ! Single source of truth
  24. Domain data flow D A T A action P R

    E S E N TA T
 I
 O N D O M A I N action reaction reaction
  25. Final advices ! Be pragmatic ◦ Some compromises are ok

    ◦ Perfect isn’t always best ! Beware of technical debt ◦ Every debt will be collected some day ! Start out small ◦ Get a feel for the structure ◦ Decide on whether it’s the best approach for your app ! Don’t forget the tests!