Skip to content

Instantly share code, notes, and snippets.

@RubyLichtenstein
Created December 27, 2018 10:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RubyLichtenstein/fa89f60b2c67b90c8af14e5dbc312f42 to your computer and use it in GitHub Desktop.
Save RubyLichtenstein/fa89f60b2c67b90c8af14e5dbc312f42 to your computer and use it in GitHub Desktop.
package com.tandoo.android.libs.repo
import android.content.Context
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.DocumentChange
import com.tandoo.android.featuers.user.Cacheable
import com.tandoo.android.firebase.firestoreInstance
import com.tandoo.android.libs.mapParallel
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.tasks.await
import kotlinx.serialization.KSerializer
import timber.log.Timber
interface IRepo<T : Cacheable> {
suspend fun getDoc(doc: String): T?
suspend fun getDocCache(doc: String): T?
suspend fun getDocCacheOrReal(doc: String): T?
suspend fun getCollectionCached(): List<T>
suspend fun getCollection(): List<T>
suspend fun delete(doc: String)
suspend fun setDoc(doc: String, any: T)
suspend fun getSubCollection(ids: List<String>): List<T>
suspend fun getSubCollectionCached(ids: List<String>): List<T>
fun observeDoc(doc: String): ReceiveChannel<T>
}
open class FirestoreCoroutines<T : Cacheable>(
val ref: String,
val collection: CollectionReference = firestoreInstance().collection(ref),
val context: Context,
val serializer: KSerializer<T>,
val clazz: Class<T>
) : IRepo<T> {
protected val dataCache = DataCache(context, serializer, clazz)
override suspend fun getDoc(doc: String): T? {
Timber.i("getDoc: collection: $collection, doc: $doc")
return collection.document(doc).get().await().toObject(clazz)?.also {
val cacheKey = createCacheKey(ref, doc)
dataCache.set(cacheKey, it)
}
}
override suspend fun getDocCacheOrReal(doc: String): T? {
return getDocCache(doc) ?: getDoc(doc)
}
override suspend fun getDocCache(doc: String): T? {
Timber.i("getDocCache: collection: $collection, doc: $doc")
val cacheKey = createCacheKey(ref, doc)
return dataCache.get(cacheKey)
}
private fun createCacheKey(ref: String, doc: String) =
"${ref.toLowerCase()}${doc.toLowerCase()}"
private suspend fun cacheList(collection: List<T>) {
dataCache.setList(ref, collection)
collection.forEach {
val cacheKey = createCacheKey(ref, it.id)
dataCache.set(cacheKey, it)
}
}
override suspend fun getSubCollection(ids: List<String>): List<T> {
return ids.mapParallel { getDoc(it) }.filterNotNull()
}
override suspend fun getSubCollectionCached(ids: List<String>): List<T> {
return ids.mapParallel { getDocCache(it) }.filterNotNull()
}
override suspend fun getCollectionCached(): List<T> {
Timber.i("getCollectionCached: collection: ${collection.path}")
return dataCache.getList(ref).orEmpty()
}
override suspend fun getCollection(): List<T> {
Timber.i("getCollection: collection: ${collection.path}")
return collection.get().await().toObjects(clazz)
.also { cacheList(it) }
}
override suspend fun setDoc(doc: String, any: T) {
Timber.i("setDoc: collection: $collection, doc: $doc")
collection.document(doc).set(any).await()
}
override suspend fun delete(doc: String) {
Timber.i("delete: collection: $collection, doc: $doc")
collection.document(doc).delete().await()
}
suspend fun update(doc: String, updateFieldsMap: Map<String, Any>) {
Timber.i("update: collection: $collection, doc: $doc, updateFieldsMap: $updateFieldsMap")
collection.document(doc).update(updateFieldsMap).await()
}
override fun observeDoc(doc: String): ReceiveChannel<T> {
Timber.i("observeDoc: collection: $collection, doc: $doc")
val channel = Channel<T>()
collection.document(doc).addSnapshotListener { querySnapshot, exception ->
exception?.let {
channel.close(it)
return@addSnapshotListener
}
if (querySnapshot == null) {
channel.close()
return@addSnapshotListener
}
querySnapshot.toObject(clazz)?.let {
channel.sendBlocking(it)
}
}
return channel
}
data class ChildEvent<T>(val child: T, val type: DocumentChange.Type)
fun observeAddedChildEvent(): ReceiveChannel<T> =
observeChildEvent()
.filter { it.type == DocumentChange.Type.ADDED }
.map { it.child }
fun observeChildEvent(): ReceiveChannel<ChildEvent<T>> {
val channel = Channel<ChildEvent<T>>()
collection.addSnapshotListener { querySnapshot, exception ->
exception?.let {
channel.close(it)
return@addSnapshotListener
}
if (querySnapshot == null) {
channel.close()
return@addSnapshotListener
}
querySnapshot.documentChanges
.mapNotNull { documentChange ->
ChildEvent(documentChange.document.toObject(clazz), documentChange.type)
}
.forEach {
channel.sendBlocking(it)
}
}
return channel
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment