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

[Devfest Kenya] Firebase + Kotlin Extensions, C...

[Devfest Kenya] Firebase + Kotlin Extensions, Coroutines & Flows

The slides for the talk I gave at Devfest Kenya 2020

The talk can be found on Youtube: https://www.youtube.com/watch?v=2ETXiioIVUw

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. // 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. //

    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. // 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. // 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 // 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.

    // 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.

    // 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. Kotlin Language Features (the 404s in Firebase KTX) • Kotlin

    Coroutines • Asynchronous Flows • Sealed Classes
  13. // 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) }
  14. // 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' }
  15. // 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() } }
  16. Asynchronous Flow Returns multiple asynchronously computed values. // Example (No

    pre-computing) fun simple(): List<Int> = listOf(1, 2, 3) fun main() { simple().forEach { value -> println(value) } }
  17. Asynchronous Flow Returns multiple asynchronously computed values. // 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
  18. Asynchronous Flow Returns multiple asynchronously computed values. // 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>
  19. Asynchronous Flow Returns multiple asynchronously computed values. // 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) } }
  20. 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
  21. 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 } } }
  22. 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) { } })
  23. 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 }
  24. 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() } }
  25. 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) } }
  26. 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