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

[Devfest South Africa] Firebase + Kotlin Extensions, Coroutines & Flows

[Devfest South Africa] Firebase + Kotlin Extensions, Coroutines & Flows

Slides for the talk I delivered at Devfest South Africa 2020

Rosário Pereira Fernandes

October 17, 2020
Tweet

More Decks by Rosário Pereira Fernandes

Other Decks in Programming

Transcript

  1. // 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()
  2. // 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" } }
  3. Kotlin Language Features (the ones present in Firebase KTX) •

    Kotlin Extensions • Object Declarations • Inline Functions • Reified Type Parameters • Type-safe Builders • Destructuring Declarations • Sequences
  4. Kotlin Extensions Provides the ability to extend a class with

    new functionality without having to inherit from the class. https://kotlinlang.org/docs/referenc e/extensions.html // 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 }
  5. Object Declarations Declares a class using the Singleton pattern. https://kotlinlang.org/docs/referenc

    e/object-declarations.html#object- declarations // 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
  6. 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)
  7. Inline Functions Tells the compiler to NOT treat our function

    as a Higher-Order function. https://kotlinlang.org/docs/referenc e/inline-functions.html // 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() }
  8. Reified Type Parameters Allows us to access a type passed

    as a parameter. https://kotlinlang.org/docs/referenc e/inline-functions.html#reified-typ e-parameters // from firebase-firestore inline fun <reified T> 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<Person>()
  9. Type-safe Builders Allow creating Kotlin-based domain-specific languages (DSLs) suitable for

    building complex hierarchical data structures in a semi-declarative way https://kotlinlang.org/docs/referenc e/type-safe-builders.html // 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” } }
  10. Destructuring Declarations Destructures an object into multiple variables at once.

    https://kotlinlang.org/docs/referenc e/multi-declarations.html // 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") }
  11. Destructuring Declarations Destructures an object into multiple variables at once.

    https://kotlinlang.org/docs/referenc e/multi-declarations.html // 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
  12. Sequences Similar to Iterable, but multi-step processing is executed lazily

    (instead of eagerly). https://kotlinlang.org/docs/referenc e/sequences.html // 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()
  13. // 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
  14. // 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
  15. Kotlin Language Features (the 404s in Firebase KTX) • Kotlin

    Coroutines • Asynchronous Flows • Sealed Classes
  16. // 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) }
  17. // 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' }
  18. // 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() } }
  19. Asynchronous Flow Returns multiple asynchronously computed values. https://kotlinlang.org/docs/referenc e/coroutines/flow.html //

    Example (No pre-computing) fun simple(): List<Int> = listOf(1, 2, 3) fun main() { simple().forEach { value -> println(value) } }
  20. Asynchronous Flow Returns multiple asynchronously computed values. https://kotlinlang.org/docs/referenc e/coroutines/flow.html //

    Computing using Sequences fun simple(): Sequence<Int> = 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
  21. Asynchronous Flow Returns multiple asynchronously computed values. https://kotlinlang.org/docs/referenc e/coroutines/flow.html //

    Computing using suspend functions suspend fun simple(): List<Int> { delay(1000) // pretend we are computing it return listOf(1, 2, 3) } fun main() = runBlocking<Unit> { simple().forEach { value -> println(value) } } // Notice that we’re returning a List<Int>
  22. Asynchronous Flow Returns multiple asynchronously computed values. https://kotlinlang.org/docs/referenc e/coroutines/flow.html //

    Computing using Asynchronous Flow fun simple(): Flow<Int> = flow { for (i in 1..3) { delay(100) // pretend we are computing it emit(i) // emit next value } } fun main() = runBlocking<Unit> { // Collect the flow simple().collect { value -> println(value) } }
  23. 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
  24. 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 https://kotlinlang.org/docs/referenc e/sealed-classes.html // 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 } } }
  25. 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) { } })
  26. 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 }
  27. 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() } }
  28. 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) } }
  29. Bonus 1: Firebase Performance Monitor // from: https://medium.com/firebase-developers/tracking-per formance-in-kotlin-suspending-functions-c81c01f87c9 2

    inline fun <E> 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 }
  30. Bonus 2: Set Custom Keys to Crashlytics fun FirebaseCrashlytics.setCustomKeys(vararg pairs:

    Pair<String, Any>) { 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\"") } } } }
  31. Bonus 2: Set Custom Keys to Crashlytics // Usage Firebase.crashlytics.setCustomKeys(

    “str_key” to “hello”, “bool_key” to true, “int_key” to 123 )
  32. 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