Slide 1

Slide 1 text

Firebase + Kotlin Rosário Pereira Fernandes Firebase GDE @_rpfernandes Extensions, Coroutines & Flows

Slide 2

Slide 2 text

What is Firebase?

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

“Android is now Kotlin-first” Google I/O 2019

Slide 5

Slide 5 text

Firebase KTX libraries

Slide 6

Slide 6 text

Views and Opinions are my own. And do not reflect those of the Firebase Team.

Slide 7

Slide 7 text

// When not using Firebase KTX val dynamicLink = FirebaseDynamicLinks.getInstance().createDynamicLink() .setLink(Uri.parse("https://www.example.com/")) .setDomainUriPrefix("https://example.page.link") .setAndroidParameters( DynamicLink.AndroidParameters.Builder("com.example.android") .setMinimumVersion(16) .build()) .setIosParameters( DynamicLink.IosParameters.Builder("com.example.ios") .setAppStoreId("123456789") .setMinimumVersion("1.0.1") .build()) .buildDynamicLink()

Slide 8

Slide 8 text

// Using Firebase KTX val dynamicLink = Firebase.dynamicLinks.dynamicLink { link = Uri.parse("https://www.example.com/") domainUriPrefix = "https://example.page.link" androidParameters("com.example.android") { minimumVersion = 16 } iosParameters("com.example.ios") { appStoreId = "123456789" minimumVersion = "1.0.1" } }

Slide 9

Slide 9 text

Kotlin Language Features (the ones present in Firebase KTX) ● Kotlin Extensions ● Object Declarations ● Inline Functions ● Reified Type Parameters ● Type-safe Builders ● Destructuring Declarations ● Sequences

Slide 10

Slide 10 text

Kotlin Extensions Provides the ability to extend a class with new functionality without having to inherit from the class. // Extension Property val String.lastChar: Char = this[length - 1] // Extension Function fun Rectangle.getArea() { return length * width; } // DEMO fun usageDemo() { print(“Hello”.lastChar) // prints o val rect = Rectangle(2, 3) print(rect.getArea()) // prints 6 }

Slide 11

Slide 11 text

Object Declarations Declares a class using the Singleton pattern. // Example object Coin { private var coin = 0 fun getCoin(): Int = coin fun addCoin() { coin += 10 } } // Usage Coin.getCoin() // returns 0 Coin.addCoin() Coin.getCoin() // returns 10 // from firebase-common-ktx object Firebase

Slide 12

Slide 12 text

Kotlin Extensions Provides the ability to extend a class with new functionality without having to inherit from that class. val Firebase.database = FirebaseDatabase.getInstance() val Firebase.firestore = FirebaseFirestore.getInstance() fun Firebase.firestore(app: FirebaseApp) = FirebaseFirestore.getInstance(app) // Usage: val firestore = Firebase.firestore val firestore2 = Firebase.firestore(app)

Slide 13

Slide 13 text

Inline Functions Tells the compiler to NOT treat our function as a Higher-Order function. // from firebase-messaging-ktx inline fun remoteMessage( to: String, init: RemoteMessage.Builder.() -> Unit ): RemoteMessage { val builder = RemoteMessage.Builder(to) builder.init() return builder.build() } // Usage remoteMessage(“user1”) { messageId = “123” data = hashMapOf() }

Slide 14

Slide 14 text

Reified Type Parameters Allows us to access a type passed as a parameter. // from firebase-firestore inline fun DocumentSnapshot.toObject(): T? = toObject(T::class.java) // Example val dataSnapshot = // … // Usage without Firebase KTX: dataSnapshot.toObject(Person::class.java) // Usage with Firebase KTX: dataSnapshot.toObject()

Slide 15

Slide 15 text

Type-safe Builders Allow creating Kotlin-based domain-specific languages (DSLs) suitable for building complex hierarchical data structures in a semi-declarative way // from firebase-dynamic-links-ktx fun FirebaseDynamicLinks.dynamicLink(init: DynamicLink.Builder.() -> Unit): DynamicLink { val builder = FirebaseDynamicLinks.getInstance().createDynami cLink() builder.init() return builder.buildDynamicLink() } // Usage Firebase.dynamicLinks.dynamicLink { androidParameters { minimumVersion = 21 } iosParameters(“bundleId”) { appStoreId = “some-id” } }

Slide 16

Slide 16 text

Destructuring Declarations Destructures an object into multiple variables at once. // Usage of firebase-storage // Without Firebase KTX uploadTask.addOnProgressListener { snapshot -> val bytesTransferred = snapshot.bytesTransferred val totalByteCount = snapshot.totalByteCount val progress = (100.0 * bytesTransferred) / totalByteCount Log.i(TAG, "Upload is $progress% done") } // With Firebase KTX uploadTask.addOnProgressListener{ (bytes, total) -> val progress = (100.0 * bytes) / total Log.i(TAG, "Upload is $progress% done") }

Slide 17

Slide 17 text

Destructuring Declarations Destructures an object into multiple variables at once. // from firebase-storage-ktx operator fun UploadTask.TaskSnapshot.component1() = bytesTransferred operator fun UploadTask.TaskSnapshot.component2() = totalByteCount operator fun UploadTask.TaskSnapshot.component3() = metadata operator fun UploadTask.TaskSnapshot.component4() = uploadSessionUri

Slide 18

Slide 18 text

Sequences Similar to Iterable, but multi-step processing is executed lazily (instead of eagerly). // Create a Sequence from Realtime Database // values (From an Iterable) var snapshot: DataSnapshot val sequence = snapshot.children.asSequence() // Create a Sequence from Cloud Firestore // values (From an Iterator) var snapshot: QuerySnapshot val sequence = snapshot.iterator().asSequence()

Slide 19

Slide 19 text

// Using an Iterable val words = "The quick brown fox jumps over the lazy dog".split(" ") val lengthsList = words.filter { println("filter: $it"); it.length > 3 } .map { println("length: ${it.length}"); it.length } .take(4) println("Lengths of first 4 words longer than 3 chars:") println(lengthsList) image from kotlinlang.org

Slide 20

Slide 20 text

// Using a Sequence val wordsSequence = "The quick brown fox jumps over the lazy dog".split(" ").asSequence() val lengthsSequence = wordsSequence.filter { println("filter: $it"); it.length > 3 } .map { println("length: ${it.length}"); it.length } .take(4) println("Lengths of first 4 words longer than 3 chars") println(lengthsSequence.toList()) // terminal operation image from kotlinlang.org

Slide 21

Slide 21 text

Kotlin Language Features (the 404s in Firebase KTX) ● Kotlin Coroutines ● Asynchronous Flows ● Sealed Classes

Slide 22

Slide 22 text

Coroutines The so called “light-weight threads”. async/await → suspend A way to escape callback hell

Slide 23

Slide 23 text

// Using the Tasks API without Coroutines usersRef.document("john").get().addOnSuccessListener { querySnapshot -> val johnUser = querySnapshot.toObject(User::class.java) friendsRef.get().addOnSuccessListener { friendSnapshot -> val friends = friendSnapshot.toObjects(Friend::class.java) showProfileAndFriends(johnUser, friends) }.addOnFailureListener { e -> displayError(e) } }.addOnFailureListener { e -> displayError(e) }

Slide 24

Slide 24 text

// Add the kotlinx-coroutines-play-services dependencies { // ... Other Dependencies … // Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' // Provides Extension Functions to use Coroutines with the Tasks API implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9' }

Slide 25

Slide 25 text

// Using the Tasks API with Coroutines suspend fun loadUserData() { try { val querySnapshot = usersRef.document("john").get().await() val johnUser = querySnapshot.toObject(User::class.java) val friendSnapshot = friendsRef.get().await() val friends = friendSnapshot.toObjects(Friend::class.java) showProfileAndFriends(johnUser, friends) } catch (e: FirebaseFirestoreException) { displayError(e) } } // Usage fun main() { GlobalScope.launch { // Avoid using GlobalScope. This is just an example loadUserData() } }

Slide 26

Slide 26 text

Asynchronous Flow Returns multiple asynchronously computed values. // Example (No pre-computing) fun simple(): List = listOf(1, 2, 3) fun main() { simple().forEach { value -> println(value) } }

Slide 27

Slide 27 text

Asynchronous Flow Returns multiple asynchronously computed values. // Computing using Sequences fun simple(): Sequence = sequence { for (i in 1..3) { Thread.sleep(100) // pretend we are computing it yield(i) // yield next value } } fun main() { simple().forEach { value -> println(value) } } // Blocks the main thread

Slide 28

Slide 28 text

Asynchronous Flow Returns multiple asynchronously computed values. // Computing using suspend functions suspend fun simple(): List { delay(1000) // pretend we are computing it return listOf(1, 2, 3) } fun main() = runBlocking { simple().forEach { value -> println(value) } } // Notice that we’re returning a List

Slide 29

Slide 29 text

Asynchronous Flow Returns multiple asynchronously computed values. // Computing using Asynchronous Flow fun simple(): Flow = flow { for (i in 1..3) { delay(100) // pretend we are computing it emit(i) // emit next value } } fun main() = runBlocking { // Collect the flow simple().collect { value -> println(value) } }

Slide 30

Slide 30 text

Asynchronous Flow How to use it with Firebase? // (hopefully) coming soon to firestore-ktx // From github: // firebase/firebase-android-sdk#1252 fun Query.toFlow() = callbackFlow { val listener = addSnapshotListener { value, error -> if (value != null) { runCatching { offer(value) } } else if (error != null) { close(error) } } awaitClose { listener.remove() } } // PS: callbackFlow is still Experimental

Slide 31

Slide 31 text

Sealed Classes Used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type // Example sealed class Result { data class Success(val data: Int) : Result data class Error(val exception: Exception) : Result object InProgress : Result } // Usage fun handleResult(result: Result) { when (result) { is Result.Success -> { // do something with result.data } is Result.Error -> { // do something with result.exception } is Result.InProgress -> { // this one has no value passed to it } } }

Slide 32

Slide 32 text

Sealed Classes Using it to simplify usage of the Realtime Database. databaseReference.addChildEventListener(object : ChildEventListener { override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) { Log.d(TAG, "onChildAdded:" + dataSnapshot.key) } override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) { } override fun onChildRemoved(dataSnapshot: DataSnapshot) { } override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) { } override fun onCancelled(databaseError: DatabaseError) { } })

Slide 33

Slide 33 text

Sealed Classes Using it to simplify usage of the Realtime Database. sealed class Child { data class Added(val dataSnapshot: DataSnapshot,val previousChildName: String?) : Child data class Changed(val dataSnapshot: DataSnapshot,val previousChildName: String?) : Child data class Removed(val dataSnapshot: DataSnapshot) : Child data class Moved(val dataSnapshot: DataSnapshot,val previousChildName: String?) : Child data class Cancelled(val databaseError: DatabaseError) : Child }

Slide 34

Slide 34 text

Sealed Classes Sealed Classes + Flow to simplify the usage of the Realtime Database. fun DatabaseReference.childrenFlow() = callbackFlow { val listener = addChildEventListener(object : ChildEventListener { override fun onChildAdded(ds: DataSnapshot, s: String?) { runCatching { offer(Child.Added(ds, s)) } } override fun onChildChanged(ds: DataSnapshot, s: String?) { runCatching { offer(Child.Changed(ds, s)) } } // ... onChildMoved, onChild Removed override fun onCancelled(databaseError: DatabaseError) { close(databaseError) } }) awaitClose { listener.remove() } }

Slide 35

Slide 35 text

Sealed Classes Sealed Classes + Flow to simplify the usage of the Realtime Database. // Usage var databaseRef: DatabaseReference = ... databaseRef.childrenFlow().collect { child -> if (child is Child.Added) { Log.d(TAG, "onChildAdded:" + child.dataSnapshot.key) } }

Slide 36

Slide 36 text

Bonus 1: Firebase Performance Monitor // from: https://medium.com/firebase-developers/tracking-per formance-in-kotlin-suspending-functions-c81c01f87c9 2 inline fun trace(name : String, block: (Trace) -> E): E { val trace = startTrace(name) return try { block(trace) } finally { trace.stop() } } // Usage trace(“http-trace”) { // run the http request }

Slide 37

Slide 37 text

Bonus 2: Set Custom Keys to Crashlytics fun FirebaseCrashlytics.setCustomKeys(vararg pairs: Pair) { for ((key, value) in pairs) { when (value) { is Boolean -> setCustomKey(key, value) is Double -> setCustomKey(key, value) is Float -> setCustomKey(key, value) is Int -> setCustomKey(key, value) is Long -> setCustomKey(key, value) is String -> setCustomKey(key, value) else -> { throw IllegalArgumentException("Illegal value type for key \"$key\"") } } } }

Slide 38

Slide 38 text

Bonus 2: Set Custom Keys to Crashlytics // Usage Firebase.crashlytics.setCustomKeys( “str_key” to “hello”, “bool_key” to true, “int_key” to 123 )

Slide 39

Slide 39 text

Additional Resources ● The Firebase KTX libraries are Open Source: https://github.com/firebase/firebase-android-sdk ● Firebase KTX Docs: https://firebaseopensource.com/projects/firebase/firebase-android-sdk/ ● Coroutines Play Services: https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx- coroutines-play-services

Slide 40

Slide 40 text

Thank You! Rosário Pereira Fernandes Firebase GDE @_rpfernandes