@@ -19,6 +19,7 @@ import com.google.firebase.dataconnect.DataSource
1919import com.google.firebase.dataconnect.QuerySubscription
2020import com.google.firebase.dataconnect.QuerySubscriptionResult
2121import com.google.firebase.dataconnect.querymgr.subscribe
22+ import com.google.firebase.dataconnect.sqlite.DataConnectCacheDatabase.SqliteSequenceNumber
2223import com.google.firebase.dataconnect.sqlite.SqliteSequencedReference
2324import com.google.firebase.dataconnect.util.SequencedReference
2425import com.google.firebase.dataconnect.util.throwIfCancellationException
@@ -27,6 +28,8 @@ import kotlinx.coroutines.channels.SendChannel
2728import kotlinx.coroutines.flow.Flow
2829import kotlinx.coroutines.flow.channelFlow
2930import kotlinx.coroutines.launch
31+ import kotlinx.coroutines.sync.Mutex
32+ import kotlinx.coroutines.sync.withLock
3033
3134internal class QuerySubscriptionImpl <Data , Variables >(
3235 override val query : QueryRefImpl <Data , Variables >,
@@ -78,34 +81,113 @@ internal class QuerySubscriptionImpl<Data, Variables>(
7881 private inner class ResultSender (
7982 val channel : SendChannel <QuerySubscriptionResultImpl >,
8083 ) {
84+ private val mutex = Mutex ()
85+ private var lastEmittedSqliteSequenceNumber: SqliteSequenceNumber ? = null
86+
8187 suspend fun onNonRealtimeUpdate (
8288 event : SequencedReference <Result <SourcedData <Data >>>,
8389 ) {
84- val (source, _, data) = event.ref.getOrNull() ? : return
85- emit(data, source)
90+ val (source, sqliteSequenceNumber, data) = event.ref.getOrNull() ? : return
91+ mutex.withLock {
92+ if (shouldEmitNonRealtime(source, sqliteSequenceNumber, lastEmittedSqliteSequenceNumber)) {
93+ emit(data, source, sqliteSequenceNumber)
94+ }
95+ }
8696 }
8797
8898 suspend fun onRealtimeUpdate (
8999 event : Result <SqliteSequencedReference <Data >>,
90100 ) {
91101 event.throwIfCancellationException()
92- val queryResult = event.map { query.QueryResultImpl (it.ref, DataSource .SERVER ) }
93- emit(queryResult)
102+
103+ mutex.withLock {
104+ if (shouldEmitRealtime(event, lastEmittedSqliteSequenceNumber)) {
105+ val queryResult = event.map { query.QueryResultImpl (it.ref, DataSource .SERVER ) }
106+ emit(queryResult, event.getOrNull()?.sqliteSequenceNumber)
107+ }
108+ }
94109 }
95110
96111 private suspend fun emit (
97112 queryResult : Result <QueryRefImpl <Data , Variables >.QueryResultImpl >,
113+ dataSqliteSequenceNumber : SqliteSequenceNumber ? ,
98114 ) {
99115 val subscriptionResult = QuerySubscriptionResultImpl (query, queryResult)
100116 channel.send(subscriptionResult)
117+ if (dataSqliteSequenceNumber != null ) {
118+ lastEmittedSqliteSequenceNumber = dataSqliteSequenceNumber
119+ }
101120 }
102121
103- private suspend fun emit (data : Data , source : DataSource ) {
104- emit(query.QueryResultImpl (data, source))
122+ private suspend fun emit (
123+ data : Data ,
124+ source : DataSource ,
125+ dataSqliteSequenceNumber : SqliteSequenceNumber ? ,
126+ ) {
127+ emit(query.QueryResultImpl (data, source), dataSqliteSequenceNumber)
105128 }
106129
107- private suspend fun emit (queryResult : QueryRefImpl <Data , Variables >.QueryResultImpl ) {
108- emit(Result .success(queryResult))
130+ private suspend fun emit (
131+ queryResult : QueryRefImpl <Data , Variables >.QueryResultImpl ,
132+ dataSqliteSequenceNumber : SqliteSequenceNumber ? ,
133+ ) {
134+ emit(Result .success(queryResult), dataSqliteSequenceNumber)
109135 }
110136 }
111137}
138+
139+ private fun shouldEmitNonRealtime (
140+ dataSource : DataSource ,
141+ dataSqliteSequenceNumber : SqliteSequenceNumber ? ,
142+ lastEmittedSqliteSequenceNumber : SqliteSequenceNumber ? ,
143+ ): Boolean {
144+ // Emit the data if `lastEmittedSqliteSequenceNumber` is null, as that indicates that there have
145+ // been no results emitted yet. Regardless of the age of the data, we may as well emit something.
146+ if (lastEmittedSqliteSequenceNumber == null ) {
147+ return true
148+ }
149+
150+ // Return an appropriate value when `dataSqliteSequenceNumber` is null. The meaning of a null
151+ // value depends on the source of the data.
152+ if (dataSqliteSequenceNumber == null ) {
153+ return when (dataSource) {
154+ // Do not emit the data because it's so old that it was saved to cache by an older version of
155+ // the SDK that lacked SqliteSequenceNumber support; therefore, the data cannot possibly be
156+ // newer than the data previously emitted with `lastEmittedSqliteSequenceNumber`.
157+ DataSource .CACHE -> false
158+ // Emit the data because either saving the data to the cache failed, or caching is not enabled
159+ // at all. Either way, there is no way to tell if this data is older than the data previously
160+ // emitted with `lastEmittedSqliteSequenceNumber`, so assume that it is newer.
161+ DataSource .SERVER -> true
162+ }
163+ }
164+
165+ // Emit the data if, and only if, it is newer than the data previously emitted with
166+ // `lastEmittedSqliteSequenceNumber`.
167+ return dataSqliteSequenceNumber > lastEmittedSqliteSequenceNumber
168+ }
169+
170+ private fun shouldEmitRealtime (
171+ event : Result <SqliteSequencedReference <* >>,
172+ lastEmittedSqliteSequenceNumber : SqliteSequenceNumber ? ,
173+ ): Boolean {
174+ // Emit failures unconditionally, since they do not have an associated SqliteSequenceNumbers.
175+ if (event.isFailure) {
176+ return true
177+ }
178+
179+ // Emit the data if `lastEmittedSqliteSequenceNumber` is null, as that indicates that there have
180+ // been no results emitted yet. Regardless of the age of the data, we may as well emit something.
181+ if (lastEmittedSqliteSequenceNumber == null ) {
182+ return true
183+ }
184+
185+ // Emit the data if its SqliteSequenceNumber is null because that means saving the data to the
186+ // cache failed. In this case there is no way to tell if this data is older than the data
187+ // previously emitted with `lastEmittedSqliteSequenceNumber`, so assume that it is newer.
188+ val dataSqliteSequenceNumber = event.getOrThrow().sqliteSequenceNumber ? : return true
189+
190+ // Emit the data if, and only if, it is newer than the data previously emitted with
191+ // `lastEmittedSqliteSequenceNumber`.
192+ return dataSqliteSequenceNumber > lastEmittedSqliteSequenceNumber
193+ }
0 commit comments