Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Design Reactive Apps in Kotlin

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Design Reactive Apps in Kotlin

Avatar for VoxxedSG

VoxxedSG

June 17, 2018
Tweet

More Decks by VoxxedSG

Other Decks in Programming

Transcript

  1. 3 BACKGROUND ▸ 9+ years in Android development ▸ Exploring

    Kotlin since 2014 ▸ Organiser of Kotlin User Group Singapore ▸ Using Kotlin in production app since 2016
  2. 4 WHAT THIS TALK IS ABOUT ▸ How to design

    your app components by example ▸ How Kotlin would help you with that ▸ Discussed solutions could be applied for most of the languages on many platforms
  3. 5 PLAN ▸ Kotlin ▸ Reactive Extensions ▸ Problem -

    solution ▸ Extending solution to handle more scenarios
  4. 6 KOTLIN ▸ Programming language by JetBrains ▸ Officially supported

    by Google ▸ Could be complied for Android/JVM/JS/Native platforms ▸ Supports lots of modern programming concepts ▸ Looks a lot like Swift
  5. 7 REACTIVE EXTENSIONS ▸ Modern approach to async programming ▸

    Extremely popular in Android community ▸ Supports by most of modern programming languages
  6. 10 IS IT SAME JOB? interface Act { val id:

    String } ▸ Should have ID
  7. 12 HOW? ▸ Requirements should be split into small pieces

    ▸ Implementation should be split into small pieces
  8. 13 IS ID ENOUGH? ▸ How we would check is

    job with same ID is running? ▸ Who will cancel duplicated jobs? ▸ How to prevent jobs from being launched without checks
  9. 14 ABSTRACT EXECUTOR: AGENT ▸ Checks if job is in

    progress ▸ Start execution if no jobs with same id is running ▸ Provides error handling callbacks ▸ Cancel current execution
  10. 15 ABSTRACT EXECUTOR: AGENT interface Agent { fun execute(executable: Act,

    e: (Throwable) -> Unit = ::logError) fun cancel(id: String) fun cancelAll() }
  11. 17 interface Act { val id: String } class CompletableAct(

    override val id: String, override val completable: Completable ) : Act class SingleAct<T : Any>( override val id: String, override val single: Single<T> ) : Act ABSTRACT EXECUTOR: AGENT
  12. class AgentImpl : Agent { val map = ConcurrentHashMap<String, Disposable>()

    fun execute(act: Act, e: (Throwable) -> Unit) = when { map.containsKey(act.id) -> log("${act.id} - in progress") else -> startExecution(act, e) .apply { log(“${act.id} - Started”) } } … AGENT IMPLEMENTATION 18
  13. class AgentImpl : Agent { … fun startExecution(act: Act, e:

    (Throwable) -> Unit) { val removeFromMap = { map.remove(act.id) } when (act) { is CompletableAct -> act.completable .doFinally(removeFromMap) .subscribe({}, e) is SingleAct<*> -> act.single .doFinally(removeFromMap) .subscribe({}, e) else -> throw IllegalArgumentException() }.let { map.put(act.id, it) } } } AGENT IMPLEMENTATION 19
  14. 21 A BIT OF EXTENSIONS fun Completable.toAct(id: String): Act =

    CompletableAct(id, this) fun <T: Any> Single<T>.toAct(id: String): Act = SingleAct(id, this)
  15. 23 RESULT val a = AgentImpl() a.execute(Completable.timer(2, SECONDS).toAct("Hello")) a.execute(Completable.timer(2, SECONDS).toAct("Hello"))

    a.execute(Completable.timer(2, SECONDS).toAct("Hello")) Hello - Act Started Hello - Act Duplicate Hello - Act Duplicate Hello - Act Finished
  16. 25 SCENARIOS ▸ Refresh:
 Cancel second refresh call ▸ User

    profile update:
 Cancel first update
  17. 26 STRATEGIES interface StrategyHolder { val strategy: Strategy } sealed

    class Strategy object KillMe : Strategy() object SaveMe : Strategy()
  18. 28 MODIFY EXISTING COMPONENTS interface Act : StrategyHolder { val

    id: String } class CompletableAct( override val id: String, override val completable: Completable, override val strategy: Strategy = SaveMe ) : Act
  19. 29 STRATEGIES override fun execute(act: Act, e: (Throwable) -> Unit)

    = when { map.containsKey(act.id) -> when (act.strategy) { KillMe -> { cancel(act.id) startExecution(act, e) } SaveMe -> log("${act.id} - Act duplicate") } else -> startExecution(act, e) }
  20. 30 RESULT val a = AgentImpl() a.execute(Completable.timer(2, SECONDS) .toAct(“Hello”, KillMe))

    a.execute(Completable.timer(2, SECONDS) .toAct(“Hello”, KillMe)) a.execute(Completable.timer(2, SECONDS) .toAct(“Hello”, KillMe)) Hello - Act Started Hello - Act Canceled Hello - Act Started Hello - Act Canceled Hello - Act Started Hello - Act Finished
  21. 31 WHAT WE ACHIEVED SO FAR? ▸ Could compare Act’s

    ▸ Actor could decide if Act’s needs to run based on id and strategy ▸ Subscriptions managed by Agent ▸ Errors handled by Agent
  22. 34 GROUPS AND GROUP STRATEGIES interface GroupStrategyHolder { val groupStrategy:

    GroupStrategy val groupKey: String } sealed class GroupStrategy object Default : GroupStrategy() object KillGroup : GroupStrategy()
  23. 35 GROUPS AND GROUP STRATEGIES interface Act : StrategyHolder, GroupStrategyHolder

    { val id: String } class CompletableAct( override val id: String, override val completable: Completable, override val strategy: Strategy = SaveMe, override val groupStrategy: GroupStrategy = Default override val groupKey: String = “” ) : Act
  24. 36 AGENT IMPLEMENTATION typealias ActKey = String typealias GroupKey =

    String typealias GroupMap = ConcurrentHashMap<ActKey, Disposable> …
 
 private val groupsMap = ConcurrentHashMap<GroupKey, GroupMap>() override fun execute(act: Act, e: (Throwable) -> Unit) { val actsMap = groupsMap[act.groupKey] ?: ConcurrentHashMap<ActKey, Disposable>() .apply { groupsMap[act.groupKey] = this } if (act.groupStrategy == KillGroup) actsMap.values.forEach { it.dispose() } … }
  25. 38 RESULT val a = AgentImpl() a.execute(Completable.timer(2, SECONDS).toAct( id =

    "Like", groupStrategy = KillGroup, groupKey = "Like-Dislike-PostId-1234")) a.execute(Completable.timer(2, SECONDS).toAct( id = "Dislike", groupStrategy = KillGroup, groupKey = "Like-Dislike-PostId-1234")) a.execute(Completable.timer(2, SECONDS).toAct( id = "Like", groupStrategy = KillGroup, groupKey = "Like-Dislike-PostId-1234")) Like - Act Started Like - Act Canceled Dislike - Act Started Dislike - Act Canceled Like - Act Started Like - Act Finished
  26. 40 PAST VIEW MODEL / PRESENTER START JOBS SUBSCRIPTIONS MANAGEMENT

    CODE DUPLICATION EXECUTION STRATEGIES LOGIC JOB GROUPS MANAGEMENT VIEW STATE LIFECYCLE
  27. 41 FUTURE VIEW MODEL / PRESENTER AGENT START JOBS SUBSCRIPTIONS

    MANAGEMENT LIFECYCLE JOB GROUPS MANAGEMENT ACT’S VIEW STATE EXECUTION STRATEGIES DECLARATIONS
  28. 44 LIFECYCLE class AgentImpl(lifecycle: Lifecycle) : Agent { init {

    lifecycle.doOnDestroy { cancelAll() } } …
  29. 44 PLUGINS FOR AGENTS override fun execute(executable: Act, eh: (Throwable)

    -> Unit): Mission { processGroupStrategy(executable) return processActStrategy(executable) }
  30. 45 PERSISTENCY ▸ Code inside separate classes ▸ Handle execution

    error ▸ Save Act to DB ▸ Restore Act from DB ▸ Act chains
  31. 46 METRICS ▸ Realtime monitor for Agent ▸ How many

    acts running ▸ Execution time ▸ Errors