Alright, I think I finally have an Android + Kotli...
# android
c
Alright, I think I finally have an Android + Kotlin related question! I'm using Firebase's firestore, and it basically has this ability to listen to continuous updates to your query. Unfortunately there's no modern kotlin version of this. "Old" callback based example:
Copy code
val docRef = db.collection("cities").document("SF")
docRef.addSnapshotListener { snapshot, e ->
    if (e != null) {
        Log.w(TAG, "Listen failed.", e)
        return@addSnapshotListener
    }

    if (snapshot != null && snapshot.exists()) {
        Log.d(TAG, "Current data: ${snapshot.data}")
    } else {
        Log.d(TAG, "Current data: null")
    }
}
How would you take this callback/listener and convert it into a main safe flowable?
e
in general, suspendCancellableCoroutine for things that fire exactly once once, callbackFlow for things that fire multiple times
c
Currently I was able to work to get to something like this
Copy code
override suspend fun requestBooks(): Flow<List<Book>> {
    return callbackFlow {
      val listener =
          FirebaseFirestore.getInstance().collection("books").addSnapshotListener { value, error
            ->
            trySend(value!!.toObjects())
          }
      awaitClose { listener.remove() }
    }
  }
but I believe this is still not main safe, AND it seems like error should be taken into account. But from the original convo I had weeks ago in #flow I thought this is what trySend was doing? Anyway. Was just curious if you saw the original addSnapshotListener code how you would make this into a flowable
c
Hm. Last updated 2 years ago. Didn't a lot of flow stuff come out sorta recently, like stateflow etc that I should use instead? Also I know its just an example, but
GlobalScope.launch()
is frowned upon right?
There are lots of examples....
But the basic concept is the same...
c
Completely understand that there are tons of examples but none of them really came from sources that I typically trust (GDE or someone well known in the community). I'm not saying that these examples are of lower quality, I'm just saying that flowables still go over my head and could use some community guidance on which way I should go.
e
IMO a library should (usually) not be exposing a SharedFlow - those belong to some scope, and such lifecycles should belong to your application
🤔 1
k
A callback flow is typical way to convert a callback API to couroutines 🤷‍♂️
e
yes, a callback flow is, but if you want a shared flow, hook it up to a scope that you own, e.g.
Copy code
firebase(...).asFlow().shareIn(lifecycleScope)
(or whatever scope is locally appropriate)
☝️ 1
c
I am sufficiently confused. 🙃 It seems like my snippet posted in this thread doesn't look completely terrible to anyone? If so, then I suppose I'll keep using that for now?
e
snippet should probably be using
trySendBlocking
to avoid dropping events, and handle errors, but otherwise seems reasonable
c
Thanks. Yeah I was pretty happy with my implementation that I ahd, but I've been seeing this ANR in crashlytics and I started doubting myself.
And I kinda dont know what to do to repro that issue + how to solve it.
e
does firestore callback happen on the main thread or what?
ANR isn't a crash, it just means main thread is blocked for too long
c
Yep. I just don't understand how deserialization is taking too long. And since "ErrorPath" is near the top, it seems to me like maybe I should be taking errors into account in that callback and not call
trySending
if an error is present?
does firestore callback happen on the main thread or what?
I don't actually know. I'm unfortunately not confident in my threading skills, but yes if i remember from my java 101 books, it seems like the response from addSnapshotListener is delivered on the main thread
According to this, the firestore network request happens in the background, but the callback returning the info does not. https://stackoverflow.com/a/67511763
e
in that case, if you're not blocking due to a full channel buffer, there's nothing you can really do about Firebase taking too long on the main thread
c
Hm. Any chance that since I'm telling it to deserialize on main thread that could be an issue if there are errors? I guess I'm asking a question that neither of us know the answer to, but I suppose I should call return if an error is present instead of forcing trySending
l
I've used this library for Firebase before. It has serialization and coroutine support built in. https://firebaseopensource.com/projects/gitliveapp/firebase-kotlin-sdk/
c
Thanks. im going to look into how they setup their flows
z
Copy code
return callbackFlow {
    val observer = SyncObserver(this)

    reference.addChildEventListener(observer)

    awaitClose {
        reference.removeEventListener(observer)
    }
}.buffer(UNLIMITED)
Using buffer so that trySend is always successful and the consumer receives each update. This is for their realtime database, but I think you get the picture 🙂 Sending with this; getOrThrow is mostly a sanity check so that it crashes if I accidentally remove the buffer.
Copy code
trySend(notification)
    .getOrThrow()
c
@Zoltan Demant where'd you get that code from? I don't see it in https://firebaseopensource.com/projects/gitliveapp/firebase-kotlin-sdk/
z
@Colton Idle I wrote it myself 🥲
👍 1