diff --git a/firebase-database/ktx/api.txt b/firebase-database/ktx/api.txt index 4f3992e2c57..612fee97f23 100644 --- a/firebase-database/ktx/api.txt +++ b/firebase-database/ktx/api.txt @@ -1,11 +1,57 @@ // Signature format: 2.0 package com.google.firebase.database.ktx { + public abstract sealed class ChildEvent { + } + + public static final class ChildEvent.Added extends com.google.firebase.database.ktx.ChildEvent { + ctor public ChildEvent.Added(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); + method @NonNull public com.google.firebase.database.DataSnapshot component1(); + method @Nullable public String component2(); + method @NonNull public com.google.firebase.database.ktx.ChildEvent.Added copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); + method @Nullable public String getPreviousChildName(); + method @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); + property @Nullable public final String previousChildName; + property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + } + + public static final class ChildEvent.Changed extends com.google.firebase.database.ktx.ChildEvent { + ctor public ChildEvent.Changed(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); + method @NonNull public com.google.firebase.database.DataSnapshot component1(); + method @Nullable public String component2(); + method @NonNull public com.google.firebase.database.ktx.ChildEvent.Changed copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); + method @Nullable public String getPreviousChildName(); + method @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); + property @Nullable public final String previousChildName; + property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + } + + public static final class ChildEvent.Moved extends com.google.firebase.database.ktx.ChildEvent { + ctor public ChildEvent.Moved(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); + method @NonNull public com.google.firebase.database.DataSnapshot component1(); + method @Nullable public String component2(); + method @NonNull public com.google.firebase.database.ktx.ChildEvent.Moved copy(@NonNull com.google.firebase.database.DataSnapshot snapshot, @Nullable String previousChildName); + method @Nullable public String getPreviousChildName(); + method @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); + property @Nullable public final String previousChildName; + property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + } + + public static final class ChildEvent.Removed extends com.google.firebase.database.ktx.ChildEvent { + ctor public ChildEvent.Removed(@NonNull com.google.firebase.database.DataSnapshot snapshot); + method @NonNull public com.google.firebase.database.DataSnapshot component1(); + method @NonNull public com.google.firebase.database.ktx.ChildEvent.Removed copy(@NonNull com.google.firebase.database.DataSnapshot snapshot); + method @NonNull public com.google.firebase.database.DataSnapshot getSnapshot(); + property @NonNull public final com.google.firebase.database.DataSnapshot snapshot; + } + public final class DatabaseKt { method @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.ktx.Firebase, @NonNull String url); method @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app); method @NonNull public static com.google.firebase.database.FirebaseDatabase database(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String url); + method @NonNull public static kotlinx.coroutines.flow.Flow getChildEvents(@NonNull com.google.firebase.database.Query); method @NonNull public static com.google.firebase.database.FirebaseDatabase getDatabase(@NonNull com.google.firebase.ktx.Firebase); + method @NonNull public static kotlinx.coroutines.flow.Flow getSnapshots(@NonNull com.google.firebase.database.Query); method public static inline T getValue(@NonNull com.google.firebase.database.DataSnapshot); method public static inline T getValue(@NonNull com.google.firebase.database.MutableData); } diff --git a/firebase-database/ktx/ktx.gradle b/firebase-database/ktx/ktx.gradle index c15a36557ef..7685a0b72af 100644 --- a/firebase-database/ktx/ktx.gradle +++ b/firebase-database/ktx/ktx.gradle @@ -17,6 +17,8 @@ plugins { id 'kotlin-android' } +group = "com.google.firebase" + firebaseLibrary { releaseWith project(':firebase-database') publishJavadoc = true @@ -50,6 +52,7 @@ dependencies { implementation project(':firebase-database') implementation 'androidx.annotation:annotation:1.1.0' implementation 'com.google.android.gms:play-services-tasks:18.0.1' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" androidTestImplementation 'junit:junit:4.12' androidTestImplementation "com.google.truth:truth:$googleTruthVersion" diff --git a/firebase-database/ktx/src/main/kotlin/com/google/firebase/database/ktx/ChildEvent.kt b/firebase-database/ktx/src/main/kotlin/com/google/firebase/database/ktx/ChildEvent.kt new file mode 100644 index 00000000000..aeadeec5f39 --- /dev/null +++ b/firebase-database/ktx/src/main/kotlin/com/google/firebase/database/ktx/ChildEvent.kt @@ -0,0 +1,43 @@ +package com.google.firebase.database.ktx + +import com.google.firebase.database.DataSnapshot + +/** + * Used to emit events about changes in the child locations of a given + * [Query] when using the [childEvents] Flow. + */ +sealed class ChildEvent { + /** + * Emitted when a new child is added to the location. + * + * @param snapshot An immutable snapshot of the data at the new child location + * @param previousChildName The key name of sibling location ordered before the new child. This + * will be null for the first child node of a location. + */ + data class Added(val snapshot: DataSnapshot, val previousChildName: String?) : ChildEvent() + + /** + * Emitted when the data at a child location has changed. + * + * @param snapshot An immutable snapshot of the data at the new data at the child location + * @param previousChildName The key name of sibling location ordered before the child. This will + * be null for the first child node of a location. + */ + data class Changed(val snapshot: DataSnapshot, val previousChildName: String?) : ChildEvent() + + /** + * Emitted when a child is removed from the location. + * + * @param snapshot An immutable snapshot of the data at the child that was removed. + */ + data class Removed(val snapshot: DataSnapshot) : ChildEvent() + + /** + * Emitted when a child location's priority changes. + * + * @param snapshot An immutable snapshot of the data at the location that moved. + * @param previousChildName The key name of the sibling location ordered before the child + * location. This will be null if this location is ordered first. + */ + data class Moved(val snapshot: DataSnapshot, val previousChildName: String?) : ChildEvent() +} diff --git a/firebase-database/ktx/src/main/kotlin/com/google/firebase/database/ktx/Database.kt b/firebase-database/ktx/src/main/kotlin/com/google/firebase/database/ktx/Database.kt index 4e607399cbe..b200948bf49 100644 --- a/firebase-database/ktx/src/main/kotlin/com/google/firebase/database/ktx/Database.kt +++ b/firebase-database/ktx/src/main/kotlin/com/google/firebase/database/ktx/Database.kt @@ -18,12 +18,20 @@ import androidx.annotation.Keep import com.google.firebase.FirebaseApp import com.google.firebase.components.Component import com.google.firebase.components.ComponentRegistrar +import com.google.firebase.database.ChildEventListener import com.google.firebase.database.DataSnapshot +import com.google.firebase.database.DatabaseError import com.google.firebase.database.FirebaseDatabase import com.google.firebase.database.GenericTypeIndicator import com.google.firebase.database.MutableData +import com.google.firebase.database.Query +import com.google.firebase.database.ValueEventListener import com.google.firebase.ktx.Firebase import com.google.firebase.platforminfo.LibraryVersionComponent +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.callbackFlow /** Returns the [FirebaseDatabase] instance of the default [FirebaseApp]. */ val Firebase.database: FirebaseDatabase @@ -59,6 +67,68 @@ inline fun MutableData.getValue(): T? { return getValue(object : GenericTypeIndicator() {}) } +/** + * Starts listening to this query and emits its values via a [Flow]. + * + * - When the returned flow starts being collected, a [ValueEventListener] will be attached. + * - When the flow completes, the listener will be removed. + */ +val Query.snapshots + get() = callbackFlow { + val listener = addValueEventListener(object : ValueEventListener { + override fun onDataChange(snapshot: DataSnapshot) { + repo.scheduleNow { + trySendBlocking(snapshot) + } + } + + override fun onCancelled(error: DatabaseError) { + cancel(message = "Error getting Query snapshot", cause = error.toException()) + } + }) + awaitClose { removeEventListener(listener) } + } + +/** + * Starts listening to this query's child events and emits its values via a [Flow]. + * + * - When the returned flow starts being collected, a [ChildEventListener] will be attached. + * - When the flow completes, the listener will be removed. + */ +val Query.childEvents + get() = callbackFlow { + val listener = addChildEventListener(object : ChildEventListener { + override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) { + repo.scheduleNow { + trySendBlocking(ChildEvent.Added(snapshot, previousChildName)) + } + } + + override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) { + repo.scheduleNow { + trySendBlocking(ChildEvent.Changed(snapshot, previousChildName)) + } + } + + override fun onChildRemoved(snapshot: DataSnapshot) { + repo.scheduleNow { + trySendBlocking(ChildEvent.Removed(snapshot)) + } + } + + override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) { + repo.scheduleNow { + trySendBlocking(ChildEvent.Moved(snapshot, previousChildName)) + } + } + + override fun onCancelled(error: DatabaseError) { + cancel(message = "Error getting Query childEvent", cause = error.toException()) + } + }) + awaitClose { removeEventListener(listener) } + } + internal const val LIBRARY_NAME: String = "fire-db-ktx" /** @suppress */