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

Design Reactive Apps in Kotlin

Design Reactive Apps in Kotlin

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