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

Let's Talk Composing UI :: DevFest New Delhi

Let's Talk Composing UI :: DevFest New Delhi

Visit: https://rivu.dev for more info.
While Developing Apps, following a Reactive Architecture (for example MVI, Mobius, Redux and even MVVM) & Single Source of Truth can get you some big wins including but not limited to, Loose Coupling & Separation of Concerns, Code Testability and easy debugging, unidirectional data, etc. However, unlike Web, where Reactive Architectures are the norm, in Android, we need to opt for Reactive Architecture considering few tradeoffs (time to market, learning curve, etc.), as it’s not natural in Android.

What do I mean by saying Reactive architecture is not natural in Android? Just like web frontends, Android apps revolve around the UI, everything we do in our Android apps has some direct or indirect relation with the UI, and the Android UI framework is imperative itself.
While the Android Platform team kept adding more and more types of Views (such as Constraint Layout, RecyclerView or more recent Motion Layout), they didn’t change the nature of UI framework itself since the beginning of android development. This was majorly due to language and tooling limitations (limited by the technologies of that time).

Google announced official support for Kotlin 2 years back, and now major numbers of professional Android Developers worldwide already adopted Kotlin, which made it easier for Google to go Kotlin first in this IO19. Kotlin comes with many perks, some of them are Functional Programming support, compiler plugin capabilities and most importantly huge support on building DSLs at ease.

What relation does Koltin have with Reactive applications? In this IO19, Google announced Jetpack Compose (https://developer.android.com/jetpack/compose/), a new (still-in-development) next generation, Kotlin based, reactive cum declarative UI toolkit, backed by principals like Single Source of Truth, Unidirectional Data Flow, Functional Programming (especially function composition and effects).

This new UI toolkit would require a radical shift in our thought process about app architectures UI programming. In this talk, we would see how we can create and interact with UI with Jetpack Compose and how different it is from the present Android framework. We will also look into examples of some code patterns and ideas of from a few famous platforms such as Vue.js, React, Flutters, etc., and how these patterns and are adopted in Jetpack Compose.

Rivu Chakraborty

September 29, 2019
Tweet

More Decks by Rivu Chakraborty

Other Decks in Programming

Transcript

  1. Rivu Chakraborty About Me • https://rivu.dev • Sr Software Engineer

    (Android) - BYJU’S • Instructor - Caster.io • Google Certified Associate Android Developer • Speaks on Kotlin / Android • Author - Reactive Programming in Kotlin • Author - Functional Kotlin • Author - Hands-On Data Structures and Algorithms with Kotlin
  2. • Android Developers • Used Kotlin • Loves writing XML?

    @rivuchakraborty https://rivu.dev Before We begin
  3. • Android Developers • Used Kotlin • Loves writing XML?

    • Used / Loves Declarative UI @rivuchakraborty https://rivu.dev Before We begin
  4. • Android Developers • Used Kotlin • Loves writing XML?

    • Used / Loves Declarative UI • Functional / Reactive Programming / Using Redux @rivuchakraborty https://rivu.dev Before We begin
  5. @rivuchakraborty https://rivu.dev This Talk Covers • Why should we care

    • How to Use Jetpack Compose • How Compose Works
  6. This Talk Covers @rivuchakraborty https://rivu.dev • Why should we care

    • How to Use Jetpack Compose • How Compose Works • How to manage States with Compose
  7. Why Should We Care • Functional • Declarative • Reactive

    • UI @rivuchakraborty https://rivu.dev
  8. Why Should We Care @rivuchakraborty https://rivu.dev Card(color = cardInternalColor) {

    Padding(padding = 12.dp) { Column { Row { ... } Row { ... } } } } Declarative UI
  9. Why Should We Care @rivuchakraborty https://rivu.dev Declarative UI
 Circular Image

    imageFromResource( res = context.resources, R.drawable.my_drawable, shape = CircleBorder() )
  10. Why Should We Care • UI • Declarative • Functional

    • Reactive Functional Declarative UI is on rise to become The Way of doing UI @rivuchakraborty https://rivu.dev
  11. Why Should We Care Benefits • Separation of Concerns •

    Declarative + Functional / Reactive @rivuchakraborty https://rivu.dev
  12. Why Should We Care Benefits • Separation of Concerns •

    Declarative + Functional / Reactive • DSL @rivuchakraborty https://rivu.dev
  13. Why Should We Care Benefits • Separation of Concerns •

    Declarative + Functional / Reactive • DSL • Developer Friendly @rivuchakraborty https://rivu.dev
  14. Why Should We Care Benefits • Flattened UI • Less

    Compile Time Overhead @rivuchakraborty https://rivu.dev
  15. Why Should We Care Benefits • Flattened UI • Less

    Compile Time Overhead • Better Performance, Less Memory Wastage @rivuchakraborty https://rivu.dev
  16. Why Should We Care Benefits • Flattened UI • Less

    Compile Time Overhead • Better Performance, Less Memory Wastage • Easier State Management @rivuchakraborty https://rivu.dev
  17. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun CustomText(text:

    String) { Text(text = text, textAlign = TextAlign.Center) }
  18. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun CustomText(text:

    String) { Text(text = text, textAlign = TextAlign.Center) }
  19. CraneWrapper: Needed to buld the Foundation Might be renamed/removed later

    or get invoked by the framework itself inside `setContent()` How to use Jetpack Compose @rivuchakraborty https://rivu.dev
  20. MaterialTheme / CustomTheme: Every Root Layout (Anything inside CraneWrapper), should

    have a Theme How to use Jetpack Compose @rivuchakraborty https://rivu.dev setContent { CraneWrapper { CustomTheme { Counter() } } }
  21. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun CustomTheme(children:

    @Composable() () -> Unit) { MaterialTheme(colors=myColorList, typography = myTextStyles) { CurrentTextStyleProvider(defaultTextStyle) { children() } } }
  22. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun Counter()

    { Text( style = +themeTextStyle { h4 }, text = "Count 0" ) Button( text = "Increase", shape = CircleBorder(), onClick = { //Counter Increment Logic } ) }
  23. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun Counter()

    { Text( style = +themeTextStyle { h4 }, text = "Count 0" ) Button( text = “Increase”, shape = CircleBorder(), onClick = { //Counter Increment Logic } ) }
  24. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun Counter()

    { Text( style = +themeTextStyle { h4 }, text = "Count 0" ) Button( text = "Increase", shape = CircleBorder(), onClick = { //Counter Increment Logic } ) }
  25. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun Counter()

    { Text( style = +themeTextStyle { h4 }, text = "Count 0" ) Button( text = "Increase", shape = CircleBorder(), onClick = { //Counter Increment Logic } ) }
  26. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun Counter()

    { Column { Row { Padding(padding = 5.dp) { Text( style = +themeTextStyle { h4 }, text = "Count 0" ) } } Row { Padding(padding = 5.dp) { Button( text = "Increase", onClick = { //Counter Increment Logic } ) } } } }
  27. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun Counter()

    { Column { Row { Padding(padding = 5.dp) { Text( style = +themeTextStyle { h4 }, text = "Count 0" ) } }
  28. How to use Jetpack Compose @rivuchakraborty https://rivu.dev } } Row

    { Padding(padding = 5.dp) { Button( text = "Increase", onClick = { //Counter Increment Logic } ) } } } }
  29. Let’s write the logic for Counter How to use Jetpack

    Compose @rivuchakraborty https://rivu.dev
  30. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun Counter()

    { val countState = +state { 0 } Column { Row { Padding(padding = 5.dp) { Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) } } Row { Padding(padding = 5.dp) { Button( text = "Increase", onClick = { countState.value++ } ) } } } }
  31. How to use Jetpack Compose @rivuchakraborty https://rivu.dev @Composable fun Counter()

    { val countState = +state { 0 } Column { Row { Padding(padding = 5.dp) { Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) } } Row {
  32. How to use Jetpack Compose @rivuchakraborty https://rivu.dev } Row {

    Padding(padding = 5.dp) { Button( text = "Increase", onClick = { countState.value++ } ) } } } }
  33. Item 1 Item 2 Item 3 Empty Empty Empty Empty

    How Compose Works Gap Buffer @rivuchakraborty https://rivu.dev
  34. How Compose Works Slot Table Item 1 Item 2 Item

    3 Empty Empty Empty Empty Cu rr en t In de x @rivuchakraborty https://rivu.dev
  35. Item 1 Item 2 Item 3 Item 4 Empty Empty

    Empty Cu rr en t In de x @rivuchakraborty https://rivu.dev How Compose Works Slot Table
  36. @rivuchakraborty https://rivu.dev How Compose Works @Composable fun Counter2() { val

    countState = +state { 0 } Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) Button( text = "Increase", onClick = { countState.value++ } ) }
  37. Group Empty Empty Empty Empty Empty Empty How Compose Works

    @rivuchakraborty https://rivu.dev @Composable fun Counter2() { val countState = +state { 0 } Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) Button( text = "Increase", onClick = { countState.value++ } ) }
  38. Group State(0) Empty Empty Empty Empty Empty How Compose Works

    @rivuchakraborty https://rivu.dev @Composable fun Counter2() { val countState = +state { 0 } Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) Button( text = "Increase", onClick = { countState.value++ } ) }
  39. Group State(0) Group Empty Empty Empty Empty How Compose Works

    @rivuchakraborty https://rivu.dev @Composable fun Counter2() { val countState = +state { 0 } Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) Button( text = "Increase", onClick = { countState.value++ } ) }
  40. Group State(0) Group “Count 0” Empty Empty Empty How Compose

    Works @rivuchakraborty https://rivu.dev @Composable fun Counter2() { val countState = +state { 0 } Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) Button( text = "Increase", onClick = { countState.value++ } ) }
  41. Group State(0) Group “Count 0” Group Empty Empty How Compose

    Works @rivuchakraborty https://rivu.dev @Composable fun Counter2() { val countState = +state { 0 } Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) Button( text = "Increase", onClick = { countState.value++ } ) }
  42. Group State(0) Group “Count 0” Group {...} Empty How Compose

    Works @rivuchakraborty https://rivu.dev @Composable fun Counter2() { val countState = +state { 0 } Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) Button( text = "Increase", onClick = { countState.value++ } ) }
  43. Group State(1) Group “Count 1” Group {...} Empty Recompose How

    Compose Works @rivuchakraborty https://rivu.dev @Composable fun Counter2() { val countState = +state { 0 } Text( style = +themeTextStyle { h4 }, text = "Count ${countState.value}" ) Button( text = "Increase", onClick = { countState.value++ } ) }
  44. View Tree with XML in Present Day View Group 1

    View Group 2 View Group 3 View Group 4 Image View Button Text View Toolbar @rivuchakraborty https://rivu.dev How Compose Works
  45. Slot Table Group State(1) Group “Count 1” “Group” {...} Empty

    Cu rr en t In de x How Compose Works @rivuchakraborty https://rivu.dev
  46. How to Manage States with Compose How should we align

    our thinking to work with Functional Declarative UIs
  47. How to Manage States with Compose State View Action Reactive

    - Redux, Mobx, MVI, Mobius, MVPI, MVVMI ... @rivuchakraborty https://rivu.dev
  48. State How to Manage States with Compose @rivuchakraborty https://rivu.dev sealed

    class ViewState: BaseState { object Loading: ViewState() data class Success(val result: Data): ViewState() data class Failure(val error: Throwable): ViewState() }
  49. View How to Manage States with Compose /////MVIActivity.kt override fun

    intents(): Observable<HomeIntents> { ... } override fun bind() { viewModel.processIntents(intents()) viewModel.states().observe(this, Observer { ... } ) }
  50. ViewModel How to Manage States with Compose private val intentsSubject:

    PublishSubject<I> = PublishSubject.create() val statesObservable: Flowable<S> by lazy { intentsSubject .map(...) ... } override fun processIntents(intents: Observable<I>) { intents.subscribe(intentsSubject) }
  51. ViewModel How to Manage States with Compose ... } override

    fun processIntents(intents: Observable<I>) { intents.subscribe(intentsSubject) } override fun states(): LiveData<S> { return LiveDataReactiveStreams.fromPublisher<S>(statesObservable) }
  52. The Problem How to Manage States with Compose etSearch.addTextChangeListener(object: TextWatcher

    { override fun beforeTextChanged(c:CharSequence, start: Int, count: Int, after: Int) { ... } override fun onTextChanged(c:CharSequence, start: Int, count: Int, after: Int) { ... } override fun afterTextChanged(s: Editable) { ... } }
  53. Let’s build a statefull App How to Manage States with

    Compose @rivuchakraborty https://rivu.dev
  54. How to Manage States with Compose @rivuchakraborty https://rivu.dev data class

    User( var name: String="", var email: String="" )
  55. How to Manage States with Compose @rivuchakraborty https://rivu.dev sealed class

    UserViewState { object Loading: UserViewState() data class Success( val user: User ): UserViewState() data class Failure( val errorDetails: String ): UserViewState() }
  56. How to Manage States with Compose @rivuchakraborty https://rivu.dev class UserViewModel:

    BaseViewModel<UserIntents, UserViewState, UserActions, UserResults> { override fun states(): LiveData<UserViewState> }
  57. Success How to Manage States with Compose @rivuchakraborty https://rivu.dev @Composable

    fun Body(user: User, onReloadClick: () -> Unit) { Padding(padding = 16.dp) { Column { Row { Text { Span(text = "Name: ",style = +themeTextStyle { body1 } ) Span(text = user.name, style = +themeTextStyle { h6 } ) } }
  58. Span(text = user.name, style = +themeTextStyle { h6 } )

    } } Row { Text { Span(text = "Email: ",style = +themeTextStyle { body1 } ) Span(text = user.email, style = +themeTextStyle { body1 } ) } } } } Success How to Manage States with Compose @rivuchakraborty https://rivu.dev
  59. Span(text = "Email: ",style = +themeTextStyle { body1 } )

    Span(text = user.email, style = +themeTextStyle { body1 } ) } } } } Align(Alignment.Center) { ReloadButton(onReloadClick) } } Success How to Manage States with Compose @rivuchakraborty https://rivu.dev
  60. Success How to Manage States with Compose @rivuchakraborty https://rivu.dev @Composable

    fun Body(user: User, onReloadClick: () -> Unit) { Padding(padding = 16.dp) { Column { Row { Text { Span(text = "Name: ",style = +themeTextStyle { body1 }) Span(text = user.name, style = +themeTextStyle { h6 }) } } Row { Text { Span(text = "Email: ",style = +themeTextStyle { body1 }) Span(text = user.email, style = +themeTextStyle { h6 }) } } } } Align(Alignment.Center) { ReloadButton(onReloadClick) } }
  61. Error How to Manage States with Compose @rivuchakraborty https://rivu.dev @Composable

    fun ErrorBody(onReloadClick: () -> Unit) { Align(Alignment.Center) { Column { Row { Text(text = "Load failed", style = +themeTextStyle { body1 } ) } Row { ReloadButton(onReloadClick) } } } }
  62. Reload Button How to Manage States with Compose @rivuchakraborty https://rivu.dev

    @Composable fun ReloadButton(onReloadClick: () -> Unit) { Button(onClick = onReloadClick, text = "Reload", color = +themeColor { lightGray }) }
  63. How to Manage States with Compose @rivuchakraborty https://rivu.dev sealed class

    UserViewState { object Loading: UserViewState() data class Success( val user: User ): UserViewState() data class Failure( val errorDetails: String ): UserViewState() }
  64. State change In the UI Level 2 ways How to

    Manage States with Compose @rivuchakraborty https://rivu.dev sealed class UserViewState { object Loading: UserViewState() data class Success( val user: User ): UserViewState() data class Failure( val errorDetails: String ): UserViewState() }
  65. Activity How to Manage States with Compose Approach 1 @rivuchakraborty

    https://rivu.dev class StateExampleActivity : Activity() { @Inject lateinit var presenter: Presenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { CraneWrapper { CustomTheme { StateComposable(presenter) } } } } }
  66. Activity How to Manage States with Compose Approach 1 @rivuchakraborty

    https://rivu.dev class StateExampleActivity : Activity() { @Inject lateinit var presenter: Presenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { CraneWrapper { CustomTheme {
  67. lateinit var presenter: Presenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    setContent { CraneWrapper { CustomTheme { StateComposable(presenter) } } } } } Activity Approach 1 @rivuchakraborty https://rivu.dev
  68. How to Manage States with Compose Approach 1 Root Composable

    @rivuchakraborty https://rivu.dev @Composable fun StateComposable( presenter: Presenter, stateModel: UserViewState = UserViewState.Loading ) { val data = +state { stateModel } fun onReloadClick() { data.value = UserViewState.Loading }
  69. ) { val data = +state { stateModel } fun

    onReloadClick() { data.value = UserViewState.Loading } when (stateModel) { is UserViewState.Loading -> { +onCommit { presenter.getUserAsync() .subscribe { userViewState -> Approach 1 Root Composable @rivuchakraborty https://rivu.dev
  70. } when (stateModel) { is UserViewState.Loading -> { +onCommit {

    presenter.getUserAsync() .subscribe { userViewState -> data.value = userViewState } } Align(Alignment.Center) { Text(text = "Loading", style = +themeTextStyle { h2 }) Approach 1 Root Composable @rivuchakraborty https://rivu.dev
  71. return } is UserViewState.Failure -> ErrorBody(::onReloadClick) is UserViewState.Success -> {

    val user = stateModel.user Body(user, ::onReloadClick) } } } Approach 1 Root Composable @rivuchakraborty https://rivu.dev
  72. Model Class How to Manage States with Compose @rivuchakraborty https://rivu.dev

    Approach 2 @Model class ViewStateModel( var user: User? = null, var error: String = "", var isLoading: Boolean = false )
  73. Activity How to Manage States with Compose Approach 2 @rivuchakraborty

    https://rivu.dev class ModelClassExampleActivity : Activity() { var stateModel = ViewStateModel(isLoading = true) @Inject lateinit var presenter: Presenter val compositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
  74. val compositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)

    setContent { CraneWrapper { CustomTheme { ModelComposable(stateModel,::loadData) } } } loadData() } Activity Approach 2 @rivuchakraborty https://rivu.dev
  75. } private fun loadData() { stateModel.isLoading = true val disposable

    = presenter.getUserAsync() .subscribe { when (it) { is UserViewState.Loading -> stateModel.isLoading = true is UserViewState.Failure -> stateModel.error = it.errorDetails is UserViewState.Success -> stateModel.user = it.user } Approach 2 @rivuchakraborty https://rivu.dev Activity
  76. Composable How to Manage States with Compose @rivuchakraborty https://rivu.dev Approach

    2 @Composable fun ModelComposable(viewStateModel: ViewStateModel, onReloadClick: () -> Unit) { val user = viewStateModel.user if(viewStateModel.isLoading) { Align(Alignment.Center) { Text(text = "Loading", style = +themeTextStyle { h2 }) } } else if(viewStateModel.error.isNotBlank() || user == null) {
  77. if(viewStateModel.isLoading) { Align(Alignment.Center) { Text(text = "Loading", style = +themeTextStyle

    { h2 }) } } else if(viewStateModel.error.isNotBlank() || user == null) { ErrorBody(onReloadClick) } else { Body(user, onReloadClick) } } @rivuchakraborty https://rivu.dev
  78. Let’s Talk Composing UI
 Take Aways @rivuchakraborty https://rivu.dev ✓Try out

    Jetpack Compose ✓Play with +state, +model ✓Try @Model class ✓Try using your preferred Architecture Pattern with Jetpack Compose
  79. Let’s Talk Composing UI
 Resources ➢ https://developer.android.com/jetpack/compose ➢ http://bit.ly/composefirstprinciple ➢

    http://bit.ly/contentondeclarativeUI ➢ https://speakerdeck.com/lelandrichardson/react-meet-compose ➢ https://rivu.dev/writing-android-ui-code-in-jetpack-compose/ ➢ https://rivu.dev/jetpack-compose-managing-states/ ➢ https://fragmentedpodcast.com/episodes/172/ @rivuchakraborty https://rivu.dev