Slide 1

Slide 1 text

luca_nicolett MVI with Jetpack Compose

Slide 2

Slide 2 text

MVI luca_nicolett

Slide 3

Slide 3 text

luca_nicolett MVI VIEW INTENT MODEL T USER Updates Manipulates Interacts Sees

Slide 4

Slide 4 text

luca_nicolett MVI Core principles: • Unidirectional • Immutability • Reactiveness • Functional

Slide 5

Slide 5 text

luca_nicolett MVI

Slide 6

Slide 6 text

luca_nicolett MVI UI REDUCER BUSINESS 
 LOGIC Actions Events State UI 
 Listens STATE

Slide 7

Slide 7 text

luca_nicolett MVI SOMETHING REDUCER ACTION
 RELAY Actions State
 Updates STATE
 RELAY TRANSFORMERS Actions States Events

Slide 8

Slide 8 text

luca_nicolett MVI • Actions • Transformers • Reducers • States

Slide 9

Slide 9 text

luca_nicolett MVI The middleware • Works as a glue • Binds actions to transformers • Transformations to reducers • Actions to reducers

Slide 10

Slide 10 text

luca_nicolett MVI perform("rating selection") .on() .withReducer(reducers::reduceRatingSelected)

Slide 11

Slide 11 text

luca_nicolett MVI perform("load logged in patient data") .on( LifecycleAction.Created::class.java, ProfileScreenAction.RetryConnection::class.java, ProfileScreenAction.MonitorReady::class.java ) .transform { transformers.loadPatient(this) } .withReducer(reducers::reduceLoggedInPatient)

Slide 12

Slide 12 text

luca_nicolett MVI perform("load logged in patient data") .on( LifecycleAction.Created::class.java, ProfileScreenAction.RetryConnection::class.java, ProfileScreenAction.MonitorReady::class.java ) .transform { transformers.loadPatient(this) } .withReducer(reducers::reduceLoggedInPatient)

Slide 13

Slide 13 text

luca_nicolett MVI How does it help? • Decoupling • Testing

Slide 14

Slide 14 text

luca_nicolett MVI

Slide 15

Slide 15 text

luca_nicolett MVI

Slide 16

Slide 16 text

luca_nicolett MVI Fragment Renderer Activity render()

Slide 17

Slide 17 text

luca_nicolett MVI private fun render(state: HomeScreenRedesignState) { loading_container .show(state.loadingState == LoadingState.Loading) unable_to_connect_error_container .show(state.loadingState == LoadingState.Error) /* ... */ }

Slide 18

Slide 18 text

luca_nicolett MVI fun View.show(isVisible: Boolean = true) { this.visibility = if (isVisible) View.VISIBLE else View.GONE }

Slide 19

Slide 19 text

luca_nicolett MVI private fun render(state: EditMoodState) { submitEditsButton?.isEnabled = state.hasAppliedChanges /* ... */ }

Slide 20

Slide 20 text

luca_nicolett MVI private fun render(state: AddMoodState) { if (state.showUrgentHelpDialog) { showUrgentHelpDialog() return } /* ... */ }

Slide 21

Slide 21 text

luca_nicolett MVI Coming soon

Slide 22

Slide 22 text

Jetpack Compose luca_nicolett

Slide 23

Slide 23 text

luca_nicolett Jetpack Compose Google I/O 2019 Android Dev Summit 2019

Slide 24

Slide 24 text

luca_nicolett Jetpack Compose Google I/O 2019 Android Dev Summit 2019

Slide 25

Slide 25 text

luca_nicolett Jetpack Compose • Declarative UI

Slide 26

Slide 26 text

luca_nicolett Jetpack Compose Declarative -> describe what you would like Procedural -> describe how to achieve it

Slide 27

Slide 27 text

luca_nicolett Android - “old”

Slide 28

Slide 28 text

luca_nicolett Flutter Column( children: [ Text('First block'), Text('Second block'), Text('third block') ] )

Slide 29

Slide 29 text

luca_nicolett Android - Jetpack Compose Column ( crossAxisAlignment = Center ) { Text("First block") Text("Second block") Text("Third block") }

Slide 30

Slide 30 text

luca_nicolett Jetpack Compose • Declarative UI • Concise & Idiomatic • Stateless or Stateful components • Reusable components • Compatible

Slide 31

Slide 31 text

luca_nicolett Jetpack Compose @Composable
 @GenerateView fun Greetings(name: String) { /* ... */ } val greetingsView = findViewById(R.id.greetings) greetingsView.name = "Luca"

Slide 32

Slide 32 text

luca_nicolett Jetpack Compose @Composable
 @GenerateView fun Greetings(name: String) { /* ... */ } val greetingsView = findViewById(R.id.greetings) greetingsView.name = "Luca"

Slide 33

Slide 33 text

luca_nicolett Jetpack Compose @Composable
 @GenerateView fun Greetings(name: String) { /* ... */ } val greetingsView = findViewById(R.id.greetings) greetingsView.name = "Luca"

Slide 34

Slide 34 text

luca_nicolett Jetpack Compose • Declarative UI • Concise & Idiomatic • Stateless or Stateful components • Reusable components • Compatible • Unbundled from the OS

Slide 35

Slide 35 text

luca_nicolett Jetpack Compose buildFeatures { // Enables Jetpack Compose for this module compose true } // Set both the Java and Kotlin compilers to target Java 8. compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" }

Slide 36

Slide 36 text

luca_nicolett Jetpack Compose buildscript { ... dependencies { classpath "org.android.tools.build:gradle:4.0.0-alpha01" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2" } }

Slide 37

Slide 37 text

luca_nicolett Jetpack Compose dependencies { // Include the following Compose toolkit dependencies. implementation "androidx.ui:ui-framework:0.1.0-dev02" implementation "androidx.ui:ui-tooling:0.1.0-dev02" implementation "androidx.ui:ui-layout:0.1.0-dev02" implementation "androidx.ui:ui-material:0.1.0-dev02" }

Slide 38

Slide 38 text

luca_nicolett Jetpack Compose dependencies { impl "androidx.compose:compose-runtime:0.1.0-dev02" }

Slide 39

Slide 39 text

luca_nicolett Jetpack Compose

Slide 40

Slide 40 text

luca_nicolett Jetpack Compose mkdir ~/bin PATH=~/bin:$PATH curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo chmod a+x ~/bin/repo

Slide 41

Slide 41 text

luca_nicolett Jetpack Compose mkdir androidx-master-dev cd androidx-master-dev repo init -u https://android.googlesource.com/platform/ manifest -b androidx-master-dev repo sync -j8 -c

Slide 42

Slide 42 text

luca_nicolett Jetpack Compose mkdir androidx-master-dev cd androidx-master-dev repo init -u https://android.googlesource.com/platform/ manifest -b androidx-master-dev repo sync -j8 -c Download the code (and grab a coffee while we pull down 6GB)

Slide 43

Slide 43 text

luca_nicolett Jetpack Compose cd path/to/checkout/frameworks/support/ ./studiow

Slide 44

Slide 44 text

luca_nicolett Jetpack Compose class MyActivity: Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyComposableFunction() } } }

Slide 45

Slide 45 text

luca_nicolett Jetpack Compose class MyActivity: Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyComposableFunction() } } }

Slide 46

Slide 46 text

luca_nicolett Jetpack Compose fun Activity.setContent(content: @Composable() () -> Unit) : CompositionContext? { /* ... */ }

Slide 47

Slide 47 text

luca_nicolett Jetpack Compose fun Activity.setContent(content: @Composable() () -> Unit) : CompositionContext? { val craneView = window.decorView .findViewById(android.R.id.content) .getChildAt(0) as? AndroidCraneView ?: AndroidCraneView(this) .also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(craneView.root, this) { WrapWithAmbients( craneView, this, coroutineContext ) {

Slide 48

Slide 48 text

luca_nicolett Jetpack Compose fun Activity.setContent(content: @Composable() () -> Unit) : CompositionContext? { val craneView = window.decorView .findViewById(android.R.id.content) .getChildAt(0) as? AndroidCraneView ?: AndroidCraneView(this) .also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(craneView.root, this) { WrapWithAmbients( craneView, this, coroutineContext ) {

Slide 49

Slide 49 text

luca_nicolett .findViewById(android.R.id.content) .getChildAt(0) as? AndroidCraneView ?: AndroidCraneView(this) .also { setContentView(it) } val coroutineContext = Dispatchers.Main return Compose.composeInto(craneView.root, this) { WrapWithAmbients( craneView, this, coroutineContext ) { content() } } } Jetpack Compose

Slide 50

Slide 50 text

luca_nicolett Jetpack Compose @Composable fun Greeting(name: String) { Text (text = "Hello $name!") }

Slide 51

Slide 51 text

luca_nicolett Jetpack Compose @Composable fun Greeting(name: String) { Text (text = "Hello $name!") } @Preview @Composable fun PreviewGreeting() { Greeting("Android") }

Slide 52

Slide 52 text

luca_nicolett Jetpack Compose

Slide 53

Slide 53 text

luca_nicolett Jetpack Compose @Composable fun Text(/* ... */) { 
 /* ... */ Draw { canvas, _ -> internalSelection.value?.let { textDelegate.paintBackground( it.min, it.max, selectionColor, canvas ) } textDelegate.paint(canvas) }
 /* ... */ }

Slide 54

Slide 54 text

luca_nicolett Jetpack Compose @Composable fun Text(/* ... */) { 
 /* ... */ Draw { canvas, _ -> internalSelection.value?.let { textDelegate.paintBackground( it.min, it.max, selectionColor, canvas ) } textDelegate.paint(canvas) }
 /* ... */ }

Slide 55

Slide 55 text

luca_nicolett Jetpack Compose @Composable fun Text(/* ... */) { 
 /* ... */ Draw { canvas, _ -> internalSelection.value?.let { textDelegate.paintBackground( it.min, it.max, selectionColor, canvas ) } textDelegate.paint(canvas) 
 // Paints the text onto the given canvas. }
 /* ... */ }

Slide 56

Slide 56 text

luca_nicolett Jetpack Compose Google I/O 2019

Slide 57

Slide 57 text

luca_nicolett Jetpack Compose

Slide 58

Slide 58 text

luca_nicolett Jetpack Compose Android Dev Summit 2019

Slide 59

Slide 59 text

luca_nicolett Jetpack Compose @Composable fun TextField(/* ... */) { // States val hasFocus = +state { false } val coords = +state { null } /* ... */ }

Slide 60

Slide 60 text

luca_nicolett Jetpack Compose @CheckResult("+") /*inline*/ fun state(/*crossinline*/ init: () -> T) = memo { State(init()) }

Slide 61

Slide 61 text

luca_nicolett Jetpack Compose /** * The State class is an @Model class meant to * wrap around a single value. * It is used in the `+state` and `+stateFor` effects. */ @Model class State @PublishedApi internal constructor(value: T) : Framed { }

Slide 62

Slide 62 text

luca_nicolett Jetpack Compose /** * [Model] can be applied to a class which represents your * application's data model, and will cause instances of the * class to become observable, such that a read of a property * of an instance of this class during the invocation of a * composable function will cause that component to be * "subscribed" to mutations of that instance. Composable * functions which directly or indirectly read properties * of the model class, the composables will be recomposed * whenever any properties of the the model are written to. */

Slide 63

Slide 63 text

luca_nicolett Jetpack Compose @Model data class TaskModel( var isDone: Boolean, val description: String )

Slide 64

Slide 64 text

luca_nicolett Jetpack Compose @Model data class TaskModel( var isDone: Boolean, val description: String ): BaseModel()

Slide 65

Slide 65 text

luca_nicolett Jetpack Compose @Model data class TaskModel( var isDone: Boolean, val description: String ): BaseModel() TaskModel.kt: (14, 3): Model objects do not support inheritance

Slide 66

Slide 66 text

luca_nicolett Jetpack Compose

Slide 67

Slide 67 text

luca_nicolett Jetpack Compose @Composable fun DrawSeekBar(x: Float) { var paint = +memo { Paint() } Draw { canvas, parentSize -> /* ... */ canvas.drawRect(Rect(/* ... */), paint) canvas.drawRect(Rect(/* ... */), paint) canvas.drawCircle(/* ... */, paint) } }

Slide 68

Slide 68 text

luca_nicolett Jetpack Compose

Slide 69

Slide 69 text

luca_nicolett Jetpack Compose @Composable fun AlertDialog( onCloseRequest: () -> Unit, title: (@Composable() () -> Unit)? = null, text: (@Composable() () -> Unit), confirmButton: (@Composable() () -> Unit), dismissButton: (@Composable() () -> Unit)?, buttonLayout: AlertDialogButtonLayout ) { Dialog(onCloseRequest = onCloseRequest) { /* ... */ } }

Slide 70

Slide 70 text

luca_nicolett Jetpack Compose @Composable fun Dialog( onCloseRequest: () -> Unit, children: @Composable() () -> Unit ) { val context = +ambient(ContextAmbient) val dialog = +memo { DialogWrapper(context, onCloseRequest) } }

Slide 71

Slide 71 text

luca_nicolett Jetpack Compose @Composable fun Dialog( onCloseRequest: () -> Unit, children: @Composable() () -> Unit ) { +onActive { dialog.show() onDispose { dialog.dismiss() dialog.disposeComposition() } } }

Slide 72

Slide 72 text

luca_nicolett Jetpack Compose @Composable fun Dialog( onCloseRequest: () -> Unit, children: @Composable() () -> Unit ) { +onCommit { dialog.setContent(children) } }

Slide 73

Slide 73 text

luca_nicolett Jetpack Compose

Slide 74

Slide 74 text

luca_nicolett Jetpack Compose @Composable fun RippleRect() { val radius = withDensity(+ambientDensity()) { TargetRadius.toPx() } val toState = +state { ButtonStatus.Initial } val rippleTransDef = +memo { createTransDef(radius.value) } val onPress: (PxPosition) -> Unit = { p -> down.x = p.x.value down.y = p.y.value toState.value = ButtonStatus.Pressed

Slide 75

Slide 75 text

luca_nicolett Jetpack Compose } val toState = +state { ButtonStatus.Initial } val rippleTransDef = +memo { createTransDef(radius.value) } val onPress: (PxPosition) -> Unit = { p -> down.x = p.x.value down.y = p.y.value toState.value = ButtonStatus.Pressed } val onRelease: () -> Unit = { toState.value = ButtonStatus.Released } PressGestureDetector(onPress, onRelease) { Container(true) {

Slide 76

Slide 76 text

luca_nicolett Jetpack Compose } val onRelease: () -> Unit = { toState.value = ButtonStatus.Released } PressGestureDetector(onPress, onRelease) { Container(true) { Transition( definition = rippleTransDef, toState = toState.value ) { state -> RippleRectFromState(state = state) } } } }

Slide 77

Slide 77 text

luca_nicolett @Composable fun RippleRectFromState(state: TransitionState) { // TODO: file bug for when "down" is not a // file level val, it's not memoized correctly val x = down.x val y = down.y val paint = Paint().apply { color = Color( alpha = getAlpha(), red = 0, Jetpack Compose

Slide 78

Slide 78 text

luca_nicolett // file level val, it's not memoized correctly val x = down.x val y = down.y val paint = Paint().apply { color = Color( alpha = getAlpha(), red = 0, green = 235, blue = 224 ) } val radius = state[radius] Jetpack Compose

Slide 79

Slide 79 text

luca_nicolett green = 235, blue = 224 ) } val radius = state[radius] Draw { canvas, _ -> canvas.drawCircle( Offset(x, y), radius, paint ) } } Jetpack Compose

Slide 80

Slide 80 text

luca_nicolett canvas.drawCircle( Offset(x, y), radius, paint ) } } fun getAlpha() = state[ androidx.ui.animation.demos.alpha ] * 255).toInt() Jetpack Compose

Slide 81

Slide 81 text

luca_nicolett Jetpack Compose @Composable fun Padding( padding: EdgeInsets, children: @Composable() () -> Unit ) { /* ... */ }

Slide 82

Slide 82 text

luca_nicolett Jetpack Compose data class PaddingModifier( val left: IntPx = 0.ipx, val top: IntPx = 0.ipx, val right: IntPx = 0.ipx, val bottom: IntPx = 0.ipx ) : LayoutModifier { /* ... */ }

Slide 83

Slide 83 text

luca_nicolett Jetpack Compose /** * An immutable chain of [modifier elements][Modifier.Element] * for use with Composables. A Composable that has a `Modifier` * can be considered decorated or wrapped by that `Modifier`. * * Composables that accept a [Modifier] as a parameter to be * applied to the whole component represented by the composable * function should name the parameter `modifier` and assign the * parameter a default value of [Modifier.None] */

Slide 84

Slide 84 text

luca_nicolett Jetpack Compose

Slide 85

Slide 85 text

All together luca_nicolett

Slide 86

Slide 86 text

luca_nicolett All together OOP approach Functional approach

Slide 87

Slide 87 text

All together Object Oriented luca_nicolett

Slide 88

Slide 88 text

luca_nicolett All together interface BaseViewState { @Composable fun buildUI() }

Slide 89

Slide 89 text

luca_nicolett All together object InProgress : BaseViewState(true, null) { @Composable override fun buildUI() { Container(expanded = true) { CircularProgressIndicator() } } }

Slide 90

Slide 90 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 viewModel = /* view model init */
 setContent {
 CustomTheme {
 render(viewModel.states())
 }
 }
 /* .. */
 }

Slide 91

Slide 91 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 viewModel = /* view model init */
 setContent {
 CustomTheme {
 render(viewModel.states())
 }
 }
 /* .. */
 }

Slide 92

Slide 92 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 viewModel = /* view model init */
 setContent {
 CustomTheme {
 render(viewModel.states())
 }
 }
 /* .. */
 }

Slide 93

Slide 93 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 viewModel = /* view model init */
 setContent {
 CustomTheme {
 render(viewModel.states())
 }
 }
 /* .. */
 }

Slide 94

Slide 94 text

luca_nicolett All together override fun render( observableState: Observable ) { val state = +observe(ViewState.Idle, observableState) state.buildUI() }

Slide 95

Slide 95 text

luca_nicolett All together override fun render( observableState: Observable ) { val state = +observe(ViewState.Idle, observableState) state.buildUI() }

Slide 96

Slide 96 text

luca_nicolett All together override fun render( observableState: Observable ) { val state = +observe(ViewState.Idle, observableState) state.buildUI() }

Slide 97

Slide 97 text

luca_nicolett All together fun observe(initialState: T, data: Observable) = effectOf { val result = +state { initialState } +onActive { val disposable = data.subscribe { newValue -> result.value = newValue } onDispose { disposable.dispose() } } } return result.value }

Slide 98

Slide 98 text

luca_nicolett All together fun observe(initialState: T, data: Observable) = effectOf { val result = +state { initialState } +onActive { val disposable = data.subscribe { newValue -> result.value = newValue } onDispose { disposable.dispose() } } } return result.value }

Slide 99

Slide 99 text

luca_nicolett All together fun observe(initialState: T, data: Observable) = effectOf { val result = +state { initialState } +onActive { val disposable = data.subscribe { newValue -> result.value = newValue } onDispose { disposable.dispose() } } } return result.value }

Slide 100

Slide 100 text

luca_nicolett All together fun observe(initialState: T, data: Observable) = effectOf { val result = +state { initialState } +onActive { val disposable = data.subscribe { newValue -> result.value = newValue } onDispose { disposable.dispose() } } } return result.value }

Slide 101

Slide 101 text

luca_nicolett All together fun observe(initialState: T, data: Observable) = effectOf { val result = +state { initialState } +onActive { val disposable = data.subscribe { newValue -> result.value = newValue } onDispose { disposable.dispose() } } } return result.value }

Slide 102

Slide 102 text

luca_nicolett All together fun observe(initialState: T, data: Observable) = effectOf { val result = +state { initialState } +onActive { val disposable = data.subscribe { newValue -> result.value = newValue } onDispose { disposable.dispose() } } } return result.value }

Slide 103

Slide 103 text

luca_nicolett All together

Slide 104

Slide 104 text

luca_nicolett All together /** * Disposes of a composition that was started using [setContent]. * This is a convenience method around [Compose.disposeComposition] */ fun Activity.disposeComposition() { val view = window .decorView .findViewById(android.R.id.content) .getChildAt(0) as? ViewGroup ?: error("No root view found") Compose.disposeComposition(view, null) }

Slide 105

Slide 105 text

luca_nicolett All together /** * Disposes any composition previously run with [container] * as the root. This will release any resources that have * been built around the composition, including all * [onDispose] callbacks that have been registered with * [CompositionLifecycleObserver] objects. */ @MainThread fun disposeComposition( container: ViewGroup, parent: CompositionReference? = null ) {

Slide 106

Slide 106 text

luca_nicolett * [onDispose] callbacks that have been registered with * [CompositionLifecycleObserver] objects. */ @MainThread fun disposeComposition( container: ViewGroup, parent: CompositionReference? = null ) { // need to remove compositionContext from context map composeInto(container, parent) { } container.setTag(TAG_ROOT_COMPONENT, null) } All together

Slide 107

Slide 107 text

luca_nicolett All together fun observe(initialState: T, data: Observable) = effectOf { val result = +state { initialState } +onActive { val disposable = data.subscribe { newValue -> result.value = newValue } onDispose { disposable.dispose() } } } return result.value }

Slide 108

Slide 108 text

All together Functional luca_nicolett

Slide 109

Slide 109 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PostListScreen( processIntents(intents()), reloadPostListIntentPublisher ) } }

Slide 110

Slide 110 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PostListScreen( processIntents(intents()), reloadPostListIntentPublisher ) } }

Slide 111

Slide 111 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PostListScreen( processIntents(intents()), reloadPostListIntentPublisher ) } }

Slide 112

Slide 112 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PostListScreen( processIntents(intents()), reloadPostListIntentPublisher ) } }

Slide 113

Slide 113 text

luca_nicolett All together fun processIntents(intents: Observable): PostListViewState { intents.subscribe(intentsObserver) return modelState()
 } }

Slide 114

Slide 114 text

luca_nicolett All together fun modelState() = +effectOf { val result = +state { PostListViewState.Idle } var disposable: Disposable? = null +onActive { disposable = intentsObserver .applyBusinessLogic() .subscribe { result.value = it } } +onDispose { disposable?.dispose() } result.value

Slide 115

Slide 115 text

luca_nicolett All together fun modelState() = +effectOf { val result = +state { PostListViewState.Idle } var disposable: Disposable? = null +onActive { disposable = intentsObserver .applyBusinessLogic() .subscribe { result.value = it } } +onDispose { disposable?.dispose() } result.value

Slide 116

Slide 116 text

luca_nicolett fun modelState() = +effectOf { val result = +state { PostListViewState.Idle } var disposable: Disposable? = null +onActive { disposable = intentsObserver .applyBusinessLogic() .subscribe { result.value = it } } +onDispose { disposable?.dispose() } result.value } All together

Slide 117

Slide 117 text

luca_nicolett val result = +state { PostListViewState.Idle } var disposable: Disposable? = null +onActive { disposable = intentsObserver .applyBusinessLogic() .subscribe { result.value = it } } +onDispose { disposable?.dispose() } result.value } All together

Slide 118

Slide 118 text

luca_nicolett +onActive { disposable = intentsObserver .applyBusinessLogic() .subscribe { result.value = it } } +onDispose { disposable?.dispose() } result.value } All together

Slide 119

Slide 119 text

luca_nicolett All together override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { PostListScreen( processIntents(intents()) ) } }

Slide 120

Slide 120 text

luca_nicolett All together @Composable fun PostListScreen( state: PostListViewState ) { when (state) { PostListViewState.InProgress -> { Container(expanded = true) { CircularProgressIndicator() } } /* ... */ } }

Slide 121

Slide 121 text

luca_nicolett All together PostListScreen(processIntents(intents()))

Slide 122

Slide 122 text

luca_nicolett All together

Slide 123

Slide 123 text

luca_nicolett All together PostListScreen( processIntents( intents() ) )

Slide 124

Slide 124 text

luca_nicolett References MVI http://hannesdorfmann.com/android/model-view-intent https://www.novatec-gmbh.de/en/blog/mvi-in-android/ https://cycle.js.org/model-view-intent.html Jetpack Compose https://www.youtube.com/watch?v=VsStyq4Lzxo https://medium.com/q42-engineering/android-jetpack-compose-895b7fd04bf4 https://blog.karumi.com/android-jetpack-compose-review https://courses.csail.mit.edu/6.831/2006/lectures/L9.pdf http://intelligiblebabble.com/compose-from-first-principles/ http://intelligiblebabble.com/content-on-declarative-ui/ https://www.youtube.com/watch? v=Q9MtlmmN4Q0&list=PLWz5rJ2EKKc_xXXubDti2eRnIKU0p7wHd&index=60 https://www.youtube.com/watch? v=SPsdRXcgqjI&list=PLWz5rJ2EKKc_xXXubDti2eRnIKU0p7wHd&index=8&t=0s https://www.youtube.com/watch? v=XPMrnR1_Biw&list=PLWz5rJ2EKKc_xXXubDti2eRnIKU0p7wHd&index=13&t=0s

Slide 125

Slide 125 text

luca_nicolett Thank you!