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

Cycle for Android

Jaewe Heo
August 03, 2016

Cycle for Android

functional and reactive programming in Kotlin

Jaewe Heo

August 03, 2016
Tweet

More Decks by Jaewe Heo

Other Decks in Programming

Transcript

  1. Cycle for Android Jaewe Heo · !iiid! · @importre Aug.

    3, 2016 functional and reactive programming in Kotlin
  2. About me • Android • Rx • Kotlin - Kotlin

    Korea • Electron - Electron Korea • React - react-photonkit • Alfred workflows
  3. // This is inside the `Foo` module fun onNetworkRequest() {

    // ... bar.incrementCounter() // ... } Example of the Imperative Programming
  4. Barח ৻ࠗ ੉߮౟ী ߈਽ೞৈ ੗न੄ ࢚కܳ ҙܻೠ׮. Reactive Programming Foo

    Bar Listenable Reactive Fooח ־ҳীѱ ৔ೱਸ ઱ח૑ ݽܲ׮.
  5. // This is inside the `Bar` module foo.addOnNetworkRequestListener { //

    `this` is Bar this.incrementCounter() } Example of the Reactive Programming
  6. Solve the cyclic dependency val proxyX = ReplaySubject.create<Any>() val y

    = human(proxyX) val x = computer(y) x.imitate(proxyX)
  7. Hello Example • ੉ܴਸ ੍਺ (read) • ݫद૑ ߸҃ (model)

    • ݫद૑ܳ ചݶী ࠁৈષ (write)
  8. HelloActivity.kt class HelloActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_hello) Cycle.run(main, DomSource()) } private val main = { sources: Sources -> val change = sources.dom().select(helloEdit).textChanges() val model = change.map(::greeting) val view = model.map { message -> onUpdateView(message) } Sinks(DomSink(view)) } private fun onUpdateView(message: CharSequence) = { helloText.text = message } }
  9. HelloActivity.kt class HelloActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_hello) Cycle.run(main, DomSource()) } private val main = { sources: Sources -> val change = sources.dom().select(helloEdit).textChanges() val model = change.map(::greeting) val view = model.map { message -> onUpdateView(message) } Sinks(DomSink(view)) } private fun onUpdateView(message: CharSequence) = { helloText.text = message } }
  10. HelloActivity.kt class HelloActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_hello) Cycle.run(main, DomSource()) } private val main = { sources: Sources -> val change = sources.dom().select(helloEdit).textChanges() val model = change.map(::greeting) val view = model.map { message -> onUpdateView(message) } Sinks(DomSink(view)) } private fun onUpdateView(message: CharSequence) = { helloText.text = message } }
  11. HelloCycleActivity.kt class HelloCycleActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_hello) cycle { val change = dom.select(helloEdit).textChanges() val model = change.map(::greeting) model.map { message -> { helloText.text = message } } } } }
  12. HelloCycleActivity.kt class HelloCycleActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_hello) cycle { val change = dom.select(helloEdit).textChanges() val model = change.map(::greeting) model.map { message -> { helloText.text = message } } } } }
  13. HelloTest.kt class HelloTest { @Test fun testGreeting() { val actual

    = greeting("cycle") val expected = "Hello, cycle!" assertEquals(expected, actual) } }
  14. BMI Example • ރޖѱ, ఃী ؀ೠ ୡӝ ч ࢸ੿ (properties)

    • SeekBarܳ ਑૒੐ (read) • BMI ҅࢑ • Ѿҗܳ ചݶী ࠁৈષ (write)
  15. BmiActivity.kt cycle { val weightProps = Observable.just(Props(min = 40, max

    = 140, value = 70)) val heightProps = Observable.just(Props(min = 140, max = 210, value = 170)) // Intent val (weightStream, heightStream) = intent(dom, heightProps, weightProps) // Model val stateStream = model(weightStream, heightStream, weightProps, heightProps) // View stateStream.map { state -> onUpdateView(state) } }
  16. BmiActivity.kt private val intent = { dom: DomSource, heightProps: Observable<Props>,

    weightProps: Observable<Props> -> val weightChangeStream = dom .select(weightSeekBar.apply { val props = weightProps.toBlocking().first() max = props.max - props.min progress = props.value - props.min }) .userChanges() val heightChangeStream = dom .select(heightSeekBar.apply { val props = heightProps.toBlocking().first() max = props.max - props.min progress = props.value - props.min }) .userChanges() Pair(weightChangeStream, heightChangeStream) }
  17. BmiActivity.kt private val model = { weightChangeStream: Observable<Int>, heightChangeStream: Observable<Int>,

    weightProps: Observable<Props>, heightProps: Observable<Props> -> Observable.combineLatest( weightChangeStream, heightChangeStream, weightProps, heightProps, ::calculateBmi) }
  18. BmiActivity.kt private fun onUpdateView(state: State) = { unwrap(weightText, heightText, bmiText)

    { w, h, b -> w.text = "Weight: ${state.weight} kg" h.text = "Height: ${state.height} cm" b.text = "Bmi: ${state.bmi}" } nah { toast(R.string.error_undefined_views) } } kotlin-unwrap
  19. Bmi.kt data class State(val weight: Int, val height: Int, val

    bmi: Int) data class Props(val min: Int, val max: Int, val value: Int) fun calculateBmi(weight: Int, height: Int, weightProps: Props, heightProps: Props): State { val realHeight = height + heightProps.min val realWeight = weight + weightProps.min val heightMeters = realHeight * 0.01F val bmi = Math.round(realWeight / (heightMeters * heightMeters)) return State(realWeight, realHeight, bmi) }
  20. REST Example • ࢜۽Ҋஜ (read) • ୡӝীח ࢜۽Ҋஜ হ੉ ࢲߡ۽

    ࠗఠ ؘ੉ఠܳ ੍যঠ ೣ • request(write) data -> response(read) data • Ѿҗܳ ചݶী ࠁৈષ (write) • ߡౡਸ ־ܴ (read) • షझ౟ܳ ڸ਑ (write) https://jsonplaceholder.typicode.com/
  21. UsersActivity.kt cycle { error = onError // Intent val usersStream

    = api.getUsers() val refreshStream = dom .select(refreshView) .refreshes() .startWith(null as Void?) // Model val modelStream = Observable .combineLatest(usersStream, refreshStream) { users, refresh -> users } // View modelStream.map { users -> onUpdateView(users) } }
  22. Extensions fun ImageView.loadUrl(imageUrl: String) = Picasso.with(context).load(imageUrl).into(this) fun Activity.toast(@StringRes id: Int)

    = Toast.makeText(this, id, Toast.LENGTH_SHORT).show() fun Activity.toast(message: String) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show() fun RecyclerView.ViewHolder.toast(message: String) = Toast.makeText(itemView.context, message, Toast.LENGTH_SHORT).show()
  23. UserViewHolder.kt itemView.locationImage.loadUrl(url) itemView.nameText.text = user.name cycle { val emailChange =

    dom.select(itemView.emailButton).clicks().map { ButtonType.EMAIL } val callChange = dom.select(itemView.callButton).clicks().map { ButtonType.CALL } val change = Observable.merge(emailChange, callChange) change.map { type -> show(user, type) } }
  24. UserViewHolder.kt private fun show(user: User, type: ButtonType) = { toast(when

    (type) { ButtonType.EMAIL -> user.email ButtonType.CALL -> user.phone }) }
  25. val coWorker = true cycle { val change = dom

    .select(toggle.apply { isChecked = true }) .checkedChanges() val model = change .map { onOff -> "উ٘۽੉٘ ѐߊ੗ܳ ݽभפ׮: $onOff" } .map { recruit -> "Riiid!\n$recruit" } model.map { message -> { checkText.text = message } } } [email protected]