Functional Reactive Programming with Kotlin on Android - Giorgio Natili - Codemotion Milan 2016

Preview:

Citation preview

+ ^ | | @giorgionatili +-------------------------------------+--------------+

FRP + KotlinA journey into new programming paradigms

and magical operators

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

About Me / :

/ :' .. / :' ( ,\ '

|( ' ( ` .---. ` \

.-..-. ( \ \ _.._.--. `. ' '._`-._..-'``__ `-. '. ..7 '. ``` /``` `'. \ \ `-.-~-.`-/ .-.:>. \__. '. \ \ | ( `'

( ( . . \ \) \ `'-._ `'(__. : \ \

.7 (_. ( \: .' , \ '- \__--': . . `'

/ ,: ,/\ /\__. . . '--'` `--' `' / | : /| / | |` | / | : ( .' .'| '. '

( / \| : '. ( \ ( \ \

\ )./-\ \ (` ,./.-''-:.

+ Engineering Manager at Akamai (NG2 on desktop and mobile)

+ VP of Engineering at Sync Think (Android, VR and Python)

+ Founder of Mobile Tea (www.meetup.com/pro/mobiletea)

+ Leader of the Boston Twitter UG

+ Organizer of DroidCon Boston (www.droidcon-boston.com)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Agenda

+ Functional Reactive Programming

+ Kotlin in a nutshell

+ RxKotlin: FRP with Android

+ Kotlin and multithreading

+ ^ | | @giorgionatili +-------------------------------------+--------------+

Functional Reactive

Programming

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

What the hell is FRP

+ FRP is a programming paradigm

+ It focuses on reacting to streams of changes (data and events)

+ It favors function composition, avoidance of global state and side effects

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Programming Paradigm

+ Humans do not think like computer so we add abstractions

+ FRP is an abstraction just like imperative programming idioms are abstractions for binary instructions

+ FRP is all about capturing evolving values (aka Observer pattern on steroids 🚀)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Streams

+ a, b, c, d are emitted values

+ X is an error

+ | is the 'completed' signal

+ ---> is the timeline

+ --a---b-c---d---X---|->

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Functions Composition

+ The values returned by any operator are chain-able (functional magic 😎)

+ The original input is immutable

val strings = listOf("one", "two", "treee", "a", "four") val filtered = strings.filter { it.length >= 3 }

.map { it.toUpperCase() }

println(filtered) // ONE, TWO, THREE, FOUR

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Disclaimer

RxJava and RxKotlin are not pure FRP, in fact, FRP involves

continuos time whereas the Reactive Extensions only deal with discrete

event over time. Anyway, the implementation and the chain-able operators make RxJava

and RxKotlin a good enough solution for Android apps.

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Installation

compile 'io.reactivex.rxjava2:rxandroid:2.0.1' compile 'io.reactivex.rxjava2:rxjava:2.0.1'

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Observable<T>

+ An Observable represents a flowing stream of values

+ UI events

+ Data processed in background

+ The most similar abstraction is Iterable<T>

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Push vs Pull

+ Observable is push based, it decides when to emit values

+ Iterable is pull based, it sits until the some ask for the next() value

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Push vs Pull // Java Range range = new Range(1, 10);

Iterator<Integer> it = range.iterator(); while(it.hasNext()) { int cur = it.next(); System.out.println(cur); }

// RxJava Observable.range(1, 10).subscribe({ println(it); });

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Observable SpecsEvery Observable can emit an arbitrary number of values optionally followed by completion or error (but not both)

interface Observer < T > { void onNext( T t) // Data void onError( Throwable t) // Error void onCompleted() // Stream completion }

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Create an Observable

Observable are lazy, creation doesn’t trigger any action

Observable < Tweet > tweets = Observable.create( s -> { getDataFromServerWithCallback( args, data -> { try { s.onNext( data ); s.onCompleted(); } catch (Throwable t) { s.onError(t); } }); });

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Subscribe to an Observable

Subscription activates the Observable

Observable < Tweet > tweets = // ... tweets.subscribe( (Tweet tweet) -> {

println( tweet ); },(Throwable t) -> {

t.printStackTrace(); });

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Cold vs Hot Observables

+ A cold Observable is entirely lazy

+ Hot Observables might already be emitting events no matter how many Subscribers they have

+ Hot Observables occur when we have absolutely no control over the source of events (aka taps)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

FlatMapTransform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable

// Returns a List of websites based on text search Observable<List<String>> query(String text);

query("Hello, world!") .flatMap(urls -> Observable.from(urls)) .subscribe(url -> System.out.println(url));

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Scan

Apply a function to each item emitted by an Observable, sequentially, and emit each successive value

+----1---2------3------4--------5-------------->

scan((x, y) -> (x + y));

+----1---3------6------10-------15------------->

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

GroupBy

Observable.from(NumbersGenerator.getList(1000)) .groupBy((i) -> 0 == (int)i % 2 ? "EVEN" : "ODD") .subscribe((group) -> { println("Key " + ((GroupedObservable)group).getKey());

((GroupedObservable)group).subscribe((x) -> println(((GroupedObservable)group).getKey() + ": " + x));

});

Divide an Observable into a set of Observables that each emit a different group of items from the original Observable, organized by key

😏⬅

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

DistinctThe Distinct operator filters an Observable by only allowing items through that have not already been emitted

Observable < Integer > randomInts = Observable.create( subscriber -> { Random random = new Random(); while (! subscriber.isUnsubscribed()) { subscriber.onNext( random.nextInt( 1000 )); } }); Observable < Integer > uniqueRandomInts = randomInts .distinct() .take( 10);

😏⬅

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Take

Emit only the first n items emitted by an Observable

Observable.interval(1, TimeUnit.SECONDS) .map { it.toInt() } .take(20) 😏⬅

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

BufferCreates a new buffer starting with the first emitted item from the source Observable, and every skip items thereafter, fills each buffer with count items

+--1----2----3----------4-----5------6------->

buffer(count=2, skip|3)

+-----1---2-----------------4-----5--------->

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Debounce

Only emit an item from an Observable if a particular timespan has passed without it emitting another item

String[] items = {"one", "two", "three", "four", "five"}; Observable.from(items) .debounce(item -> item.equals("one") ? Observable.empty() : Observable.timer(1, TimeUnit.SECONDS));

😲⬅😏⬅😏⬅

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

FRP Use Cases

+ Processing user (tap, swipe, etc.) and system events (GPS, gyroscope, etc.)

+ Responding and processing any IO event (asynchronously)

+ Handling events and data pushed from other sources

+ Many, many, many others! :)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Handling User InputObservable.create(new Observable.OnSubscribe<String>() { @Override public void call(final Subscriber<? super String> subscriber) { editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after){}

@Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {

subscriber.onNext(s.toString()); }

@Override public void afterTextChanged(final Editable s) { } }); } }) .debounce(1000, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<String>() { @Override public void call(final String s) { textView.setText("Output : " + s); } });

⬅ Create

⬅ Emit

⬅ Handle stream

⬅ Subscribe

+ ^ | | @giorgionatili +-------------------------------------+--------------+

Kotlin in a Nutshell

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Why Kotlin+ Data classes

+ Inferred datatypes

+ Nullable types

+ Arguments default value

+ Lambda expression and "elvis" operator

+ Extension functions

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Install the Plugin

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Kotlin and the JVM

* Interoperable 100%

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Nullable TypesNothing can be null in Kotlin until is not specified

val a : String = null // won't compile! var b : Int // neither! must be initialized val ok : String? = null // OK :)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Data Classes

// Java public class User { private final String nickname; public User(String nickname) { this.nickname = nickname; } public String getNickname() { return nickname; } }

// Kotlin class User (val nickname: String)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Custom Getters

class SubscribingUser(val email: String) { val nickname: String get() { return email.substringBefore("@") } }

😏⬅

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Inferred DatatypesThe data type can be inferred by the context or by the first assignment

class LengthCounter { var counter = 0 private set fun addWord(word: String) { counter += word.length } }

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Inline Functions and Default Values

+ Functions can be declared outside of a class and imported in others

+ Arguments can have default values reducing the number of methods overloads

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Elvis OperatorThis is the name used for this operator ?:, you can use it to give an alternative value in case the object is null

try { // code... } catch (e: Throwable) { Log.e("TAG", e.message ?: "Error message") }

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Lambas+ Small chunks of code that can be passed to other functions

+ The ability to treat functions as values is part of the functional programming paradigm

val people = listOf(Person("Alice", 29), Person("Bob", 31))

println(people.maxBy { it.age }) 😏⬅

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

The "apply" Function+ The apply function allows to execute multiple operations on the same object

+ The apply function converts its first argument into a receiver of the lambda passed as a second argument

fun alphabet() = StringBuilder().apply { for (letter in 'A'..'Z') { append(letter) } append("\nNow I know the alphabet!") }.toString()

😏⬅

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

The "with" FunctionThe with function works like the apply one and returns the object to which is applied

fun alphabet() = with(StringBuilder()) { for (letter in 'A'..'Z') { append(letter) } toString() }

😏⬅

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

High-Order FunctionsA higher-order function is a function that takes functions as parameters, or returns a function

fun <T> lock(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } }

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Extension Functions+ Functions that add a new behavior to a class

+ It’s a way to extend classes which lack some useful functions

fun String.lastChar(): Char = this.get(this.length - 1)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Java to Kotlin+ On the main menu, point to Code menu.

+ Choose Convert Java File to Kotlin File

+ ^ | | @giorgionatili +-------------------------------------+--------------+

Recap

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

What We Got so Far

+ FRP is a programming paradigm

+ RxJava is a library for composing asynchronous and event-based programs using observable sequences for the Java VM

+ Kotlin is a statically-typed programming language that runs on the Java Virtual Machine

+ ^ | | @giorgionatili +-------------------------------------+--------------+

RxKotlin: FRP with Android

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Concise+ The words extends and implement were replaced by a colon :

+ Methods are defined with the fun keyword, the return type is optional

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } }

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Click ListenersThe lack of lambdas in Java 7 is the reason why listeners and callbacks are so awful to write with Kotlin

myButton.setOnClickListener { navigateToDetail() }

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Toasts and Snacks fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){

Toast.makeText(this, message, duration).show() }

toast("Hello Kotlin!")

fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: Snackbar.() -> Unit) {

val snack = Snackbar.make(this, message, length) snack.f() snack.show() }

view.snack("This is my snack")

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Composability

view.snack("Snack message") { action("Action") { toast("Action clicked") } }

fun Snackbar.action(action: String, color: Int? = null, listener: (View) -> Unit) {

setAction(action, listener) color?.let { setActionTextColor(color) } }

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Android Extensions+ By adding a specific import, the plugin will be able to create a set of properties for an activity, a fragment, or even a view

+ The name of the properties will be the ones defined in the XML you are importing

import kotlinx.android.synthetic.main.activity_detail.*

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Inflate

fun ViewGroup.inflate(layoutId: Int): View {

return LayoutInflater.from(context) .inflate(layoutId, this, false)

} val view = container?.inflate(R.layout.news_fragment)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

RxBindings

import kotlinx.android.synthetic.main.activity_detail.editText

RxTextView.afterTextChangeEvents(editText) .debounce(1000, TimeUnit.MILLISECONDS) .subscribe { tvChangeEvent -> textView.text = "Output : " + tvChangeEvent.view().text }

+ ^ | | @giorgionatili +-------------------------------------+--------------+

Multithreading Made Simple

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

RxJava and Parallelization

+ A common question about RxJava is how to achieve parallelization, or emitting multiple items concurrently from an Observable.

+ This definition breaks the Observable contract which states that onNext() must be called sequentially and never concurrently

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Schedulers+ immediate(), creates and returns a Scheduler that executes work immediately on the current thread

+ trampoline(), creates and returns a Scheduler that queues work on the current thread to be executed after the current work completes

+ newThread(), creates and returns a Scheduler that creates a new Thread for each unit of work

+ computation(), creates and returns a Scheduler intended for computational work

+ io(), creates and returns a Scheduler intended for IO-bound work (unbounded, multiple threads)

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

subscribeOn()The method subscribeOn() allows you to move the execution of the Observable code into another thread and with an specific behavior

val subscription = newsManager.getNews() .subscribeOn(Schedulers.io()) .subscribe ({}, {})

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

observeOn()The observeOn() method allows to specify where to execute the functions provided in the subscribe() one

val subscription = newsManager.getNews() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe ({}, {})

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Altogether Now Observable.just("Hello World") // each subscription is going to be on a new thread .subscribeOn(Schedulers.newThread()) // observation on the main thread .observeOn(AndroidSchedulers.mainThread())) .subscribe(object:Subscriber<String>(){ override fun onCompleted() { // Completed }

override fun onError(e: Throwable?) { // Handle error here }

override fun onNext(t: String?) { Log.e("Output",t); } })

+ ^ | | @giorgionatili +-------------------------------------+--------------+

Take Aways

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Functional Reactive Programming

+ FRP doesn’t solve all the problems

+ FRP hide complexities and make our code more readable

+ Getting started with FRP is simple and fast

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

RxJava & RxAndroid

+ RxJava and RxAndroid are not a pure FRP implementation (Sodium, Reactive-banana are pure FRP)

+ RxExtensions are a tool to uniform your programming paradigm

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Kotlin

+ Kotlin run on top of the JVM

+ Kotlin can help improving the readability of your code

+ Kotlin is extremely functional, almost everything is an expression

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Trade Off

“Programmers know the benefits of everything and the tradeoffs of

nothing”

–Rich Hickey

+--------------X----X---X---X---X------------> +---------------------------------#MobileTea->

Resources+ https://github.com/miquelbeltran/rxjava-examples

+ https://github.com/marukami/RxKotlin-Android-Samples

+ https://github.com/JakeWharton/RxBinding

+ https://gist.github.com/GiorgioNatili/d4a20d4513ee544c0700854d123360d7

+ https://nilhcem.github.io/swift-is-like-kotlin/

Schedule

7:00 pm - 7:30 pm Welcome with beers and snacks

7:30 pm - 8:35 pm Screening of "Design Disruptors"

8:35 pm - 9:30 pm Networking moment and aperitif

Giovedì 1 Dicembre, ore 19:00 , Luiss Enlabs, Stazione Termini, Roma

DESIGN DISRUPTORS is a full-length documentary featuring design leaders and product designers from 15+ industry-toppling companies—valued at more than $1trillion dollars combined.

Free, but registration is required :)

–Giorgio Natili :)

“FRP programmers do it better.”