Slide 1

Slide 1 text

Coroutine + Flow

Slide 2

Slide 2 text

Coroutine + Flow = MVI ❤

Slide 3

Slide 3 text

http://hannesdorfmann.com/android/mosby3-mvi-1

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

https://xkcd.com/1312

Slide 6

Slide 6 text

https://xkcd.com/1312

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

!

Slide 9

Slide 9 text

"

Slide 10

Slide 10 text

private val disposables = CompositeDisposable() disposables += streamA().subscribe(1)1 disposables += streamB().subscribe(1)1 disposables.clear() ♻

Slide 11

Slide 11 text

Photo source info here Coroutines & Flow

Slide 12

Slide 12 text

Photo source info here Coroutines & Flow ♻

Slide 13

Slide 13 text

Photo source info here Coroutines & Flow "

Slide 14

Slide 14 text

Photo source info here Coroutines & Flow !❓

Slide 15

Slide 15 text

output input input output user device

Slide 16

Slide 16 text

output input input output user device

Slide 17

Slide 17 text

output input input output user device

Slide 18

Slide 18 text

output input input output user device

Slide 19

Slide 19 text

input output input output user device

Slide 20

Slide 20 text

Model Intent View

Slide 21

Slide 21 text

Intent View View

Slide 22

Slide 22 text

Intent View

Slide 23

Slide 23 text

Intent View

Slide 24

Slide 24 text

View MainViewEvent Intent sealed class MainViewEvent {_ object ThumbsUpClick :_MainViewEvent() object LoveItClick :_MainViewEvent() }_ View

Slide 25

Slide 25 text

View sealed class MainViewEvent {_ object ThumbsUpClick :_MainViewEvent() object LoveItClick :_MainViewEvent() }_ MainViewEvent ThumbsUpClick LoveItClick + +❤ ❤ ❤

Slide 26

Slide 26 text

View ❤ ❤ fun View.clicks(): Flow = callbackFlow { val listener = View.OnClickListener { offer(Unit) } setOnClickListener(listener) awaitClose { setOnClickListener(null) } }

Slide 27

Slide 27 text

View ❤ ❤ fun View.clicks(): Flow = callbackFlow { val listener = View.OnClickListener { offer(Unit) } setOnClickListener(listener) awaitClose { setOnClickListener(null) } }

Slide 28

Slide 28 text

View ❤ ❤ fun View.clicks(): Flow = callbackFlow { val listener = View.OnClickListener { offer(Unit) } setOnClickListener(listener) awaitClose { setOnClickListener(null) } }

Slide 29

Slide 29 text

View ❤ ❤ fun View.clicks(): Flow = callbackFlow { val listener = View.OnClickListener { offer(Unit) } setOnClickListener(listener) awaitClose { setOnClickListener(null) } } ~

Slide 30

Slide 30 text

❤ ❤ View.clicks():Flow View Flow

Slide 31

Slide 31 text

View thumbButton.clicks() map { MainViewEvent.ThumbsUpClick } Flow ❤ ❤

Slide 32

Slide 32 text

View heartButton.clicks() map { MainViewEvent.LoveItClick } ❤ ❤ Flow ❤ ❤

Slide 33

Slide 33 text

val flows = listOf( heartButton.clicks().map { MainViewEvent.LoveItClick }, thumbButton.clicks().map { MainViewEvent.ThumbsUpClick } )x return flows.asFlow().flattenMerge(flows.size)_ Flow View ❤ ❤

Slide 34

Slide 34 text

View ❤ ❤ class MainActivity {__ override_fun_viewEvents(): Flow_{ val flows = listOf( heartButton.clicks().map { MainViewEvent.LoveItClick }, thumbButton.clicks().map { MainViewEvent.ThumbsUpClick } )x return flows.asFlow().flattenMerge(flows.size)_ }_ }__

Slide 35

Slide 35 text

View ❤ ❤ class MainActivity {__ override_fun_viewEvents(): Flow_{ val flows = listOf( heartButton.clicks().map { MainViewEvent.LoveItClick }, thumbButton.clicks().map { MainViewEvent.ThumbsUpClick } )x return flows.asFlow().flattenMerge(flows.size)_ }_ }__

Slide 36

Slide 36 text

View ❤ ❤ class MainActivity {__ override_fun_viewEvents(): Flow_{ val flows = listOf( heartButton.clicks().map { MainViewEvent.LoveItClick }, thumbButton.clicks().map { MainViewEvent.ThumbsUpClick } )x return flows.asFlow().flattenMerge(flows.size)_ }_ }__

Slide 37

Slide 37 text

class MainActivity {__ override_fun_viewEvents(): Flow_{ val flows = listOf( heartButton.clicks().map { MainViewEvent.LoveItClick }, thumbButton.clicks().map { MainViewEvent.ThumbsUpClick } )x return flows.asFlow().flattenMerge(flows.size)_ }_ }__ View ❤ ❤

Slide 38

Slide 38 text

/** * This allows us to group all the_viewEvents from * one view in a single Flow. */ interface ViewEventFlow { funxviewEvents():xFlow }x class MainActivity_: ViewEventFlow {x_ override_fun_viewEvents(): Flow_{ val flows = listOf( heartButton.clicks().map { MainViewEvent.LoveItClick }, thumbButton.clicks().map { MainViewEvent.ThumbsUpClick } )x return flows.asFlow().flattenMerge(flows.size)_ }_ }__ View ❤ ❤

Slide 39

Slide 39 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent

Slide 40

Slide 40 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent

Slide 41

Slide 41 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... scope.launch_{ viewEvents() .onEach { ?_} .collect() } }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent

Slide 42

Slide 42 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... scope.launch_{ viewEvents() .onEach { ?_} .collect() } }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent ~

Slide 43

Slide 43 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... scope.launch_{ viewEvents() .onEach { ?_} .collect() } }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent ~

Slide 44

Slide 44 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... scope.launch_{ viewEvents() .onEach { ?_} .collect() } }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent ~

Slide 45

Slide 45 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... scope.launch_{ viewEvents() .onEach { ?_} .collect() } }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent ~

Slide 46

Slide 46 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... viewEvents() .onEach { ? } .launchIn(scope) }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent

Slide 47

Slide 47 text

class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... viewEvents() .onEach { event -> ? } .launchIn(scope) }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ View Intent

Slide 48

Slide 48 text

View Intent class MainActivity_: ViewEventFlow {x_ val_scope: CoroutineScope_= MainScope() override_fun_onCreate(savedInstanceState: Bundle?)_{ super.onCreate(savedInstanceState) // ... viewEvents() .onEach { event -> MainViewIntentFactory.process(event) } .launchIn(scope) }1 override_fun_onDestroy()_{ super.onDestroy() scope.cancel() }2 override_fun_viewEvents(): Flow_{ // ... }_ }__ ~

Slide 49

Slide 49 text

ModelState store x Intent View ❓ Model

Slide 50

Slide 50 text

ModelState store Model val ❤:Int val :Int

Slide 51

Slide 51 text

ModelState store Model val ❤:Int val :Int data class UpvoteModel(val hearts:Int, val thumbs:Int) (Don’t mind me. My turn is coming up soon.)

Slide 52

Slide 52 text

ModelState store Intent View Model data class UpvoteModel(val hearts:Int, val thumbs:Int) newState = oldState.copy(thumbs = thumbs + 1) // 0, 1 newState = newState.copy(hearts = hearts + 1) // 1, 1 newState = newState.copy(hearts = hearts + 1) // 2, 1 val ❤ :Int val :Int ❓

Slide 53

Slide 53 text

ModelState store Intent View Model data class UpvoteModel(val hearts:Int, val thumbs:Int) interface Intent {x fun reduce(oldState: T): T }x val ❤ :Int val :Int

Slide 54

Slide 54 text

ModelState store Intent View Model data class UpvoteModel(val hearts:Int, val thumbs:Int) interface Intent {x fun reduce(oldState: T): T }x class AddHeart():Intent {y override fun reduce(oldState: UpvoteModel)_= oldState.copy(hearts =_oldState.hearts_+ 1) }y val ❤ :Int val :Int

Slide 55

Slide 55 text

ModelState store Intent View Model data class UpvoteModel(val hearts:Int, val thumbs:Int) fun_toIntent(viewEvent: MainViewEvent):Intent_{ return when (viewEvent) {z MainViewEvent.LoveItClick ->_AddHeart() MainViewEvent.ThumbsUpClick ->_AddThumb() }z }z2 val ❤ :Int val :Int

Slide 56

Slide 56 text

interface IntentFactory_{ suspend fun_process(viewEvent:E) }y object MainViewIntentFactory :-IntentFactory_{ override suspend fun_process(viewEvent: MainViewEvent)_{ UpvoteModelStore.process(toIntent(viewEvent)) }x private fun_toIntent(viewEvent: MainViewEvent):Intent_{ return when (viewEvent) {z MainViewEvent.LoveItClick ->_AddHeart() MainViewEvent.ThumbsUpClick ->_AddThumb() }z }z2 }w Intent View ~

Slide 57

Slide 57 text

Intent View ~ interface IntentFactory_{ suspend fun_process(viewEvent:E) }y object MainViewIntentFactory :-IntentFactory_{ override suspend fun_process(viewEvent: MainViewEvent)_{ UpvoteModelStore.process(toIntent(viewEvent)) }x private fun_toIntent(viewEvent: MainViewEvent):Intent_{ return when (viewEvent) {z MainViewEvent.LoveItClick ->_AddHeart() MainViewEvent.ThumbsUpClick ->_AddThumb() }z }z2 }w

Slide 58

Slide 58 text

Intent View ~ interface IntentFactory_{ suspend fun_process(viewEvent:E) }y object MainViewIntentFactory :-IntentFactory_{ override suspend fun_process(viewEvent: MainViewEvent)_{ UpvoteModelStore.process(toIntent(viewEvent)) }x private fun_toIntent(viewEvent: MainViewEvent):Intent_{ return when (viewEvent) {z MainViewEvent.LoveItClick ->_AddHeart() MainViewEvent.ThumbsUpClick ->_AddThumb() }z }z2 }w

Slide 59

Slide 59 text

Intent View ~ interface IntentFactory_{ suspend fun_process(viewEvent:E) }y object MainViewIntentFactory :-IntentFactory_{ override suspend fun_process(viewEvent: MainViewEvent)_{ UpvoteModelStore.process(toIntent(viewEvent)) }x private fun_toIntent(viewEvent: MainViewEvent):Intent_{ return when (viewEvent) {z MainViewEvent.LoveItClick ->_AddHeart() MainViewEvent.ThumbsUpClick ->_AddThumb() }z }z2 }w

Slide 60

Slide 60 text

ModelStore RxModelStore UpvoteModelStore FlowModelStore Intent interface_ModelStore_{y suspend_fun process(intent: Intent) fun modelState(): Flow }yy override suspend fun_process(viewEvent: MainViewEvent)_{ UpvoteModelStore.process(toIntent(viewEvent)) }x (Don’t mind me. My turn is coming up soon too.) Model val ❤ :Int val :Int ModelState store + + +

Slide 61

Slide 61 text

Intent interface_ModelStore_{y suspend_fun process(intent: Intent) fun modelState(): Flow }yy Model val ❤ :Int val :Int ModelState store + + + ModelStore RxModelStore UpvoteModelStore FlowModelStore (Don’t mind me. My turn is coming up soon too.)

Slide 62

Slide 62 text

x +❤ +❤ + Intent ModelState store Hi again! interface_ModelStore_{y suspend_fun process(intent: Intent) fun modelState(): Flow }yy

Slide 63

Slide 63 text

x val intents = Channel>() +❤ +❤ + Intent ModelState store suspend fun process(intent: Intent) { intents.send(intent) } ~

Slide 64

Slide 64 text

x val intents = Channel>() +❤ +❤ + Intent ModelState store suspend fun process(intent: Intent) { intents.send(intent) } ~ val store = ConflatedBroadcastChannel(startingState) override fun modelState(): Flow { return store.asFlow() }

Slide 65

Slide 65 text

val intents = Channel>() +❤ +❤ + Intent ModelState store ❓❓❓ val store = ConflatedBroadcastChannel(startingState)

Slide 66

Slide 66 text

+❤ +❤ + Intent 0❤, 0 0❤, 0 1❤, 1 2❤, 1 ModelState store store.offer(intents.receive().reduce(store.value)) val store = ConflatedBroadcastChannel(startingState) val intents = Channel>() 0❤, 1

Slide 67

Slide 67 text

open class FlowModelStore(startingState: S) : ModelStore { private val scope = MainScope() private val intents = Channel>() private val store = ConflatedBroadcastChannel(startingState) init { // Reduce from MainScope() scope.launch { while (isActive) store.offer(intents.receive().reduce(store.value)) } } // Could be called from any coroutine scope/context. override suspend fun process(intent: Intent) { intents.send(intent) } override fun modelState(): Flow { return store.asFlow() } fun close() { intents.close() store.close() scope.cancel() } } ModelStore RxModelStore UpvoteModelStore FlowModelStore

Slide 68

Slide 68 text

interface_ModelStore_{ suspend fun process(intent: Intent) fun modelState(): Flow }x object UpvoteModelStore :_ FlowModelStore(UpvoteModel(0, 0)) open class FlowModelStore(startingState: S) : ModelStore { // ... } ModelStore FlowModelStore UpvoteModelStore

Slide 69

Slide 69 text

object UpvoteModelStore :_ FlowModelStore(UpvoteModel(0, 0)) UpvoteModelStore

Slide 70

Slide 70 text

Intent View Model val ❤ :Int val :Int ModelState store object UpvoteModelStore

Slide 71

Slide 71 text

Intent View Model val ❤ :Int val :Int ModelState store interface_ModelStore_{ fun process(intent: Intent) fun modelState(): Flow }x fun modelState(): Flow object UpvoteModelStore

Slide 72

Slide 72 text

View Model fun Flow.forCounterTextView() = onEach { model -> counterTextView.text = } // Input(s) UpvoteModelStore .modelState() .forCounterTextView() .launchIn(scope) 0❤, 1 0❤, 0 1❤, 1 2❤, 1

Slide 73

Slide 73 text

Live demo?

Slide 74

Slide 74 text

https://github.com/kanawish/upvote

Slide 75

Slide 75 text

Photo source info here Thank you! https://github.com/kanawish/upvote