@@ -169,49 +169,66 @@ public <D extends MaybeDocument> DocumentChanges computeDocChanges(
169
169
}
170
170
}
171
171
172
- if (newDoc != null ) {
173
- newDocumentSet = newDocumentSet .add (newDoc );
174
- if (newDoc .hasLocalMutations ()) {
175
- newMutatedKeys = newMutatedKeys .insert (newDoc .getKey ());
176
- } else {
177
- newMutatedKeys = newMutatedKeys .remove (newDoc .getKey ());
178
- }
179
- } else {
180
- newDocumentSet = newDocumentSet .remove (key );
181
- newMutatedKeys = newMutatedKeys .remove (key );
182
- }
172
+ boolean oldDocHadPendingMutations =
173
+ oldDoc != null && this .mutatedKeys .contains (oldDoc .getKey ());
174
+
175
+ // We only consider committed mutations for documents that were mutated during the lifetime of
176
+ // the view.
177
+ boolean newDocHasPendingMutations =
178
+ newDoc != null
179
+ && (newDoc .hasLocalMutations ()
180
+ || (this .mutatedKeys .contains (newDoc .getKey ())
181
+ && newDoc .hasCommittedMutations ()));
182
+
183
+ boolean changeApplied = false ;
184
+
183
185
// Calculate change
184
186
if (oldDoc != null && newDoc != null ) {
185
187
boolean docsEqual = oldDoc .getData ().equals (newDoc .getData ());
186
- if (!docsEqual || oldDoc .hasLocalMutations () != newDoc .hasLocalMutations ()) {
187
- // only report a change if document actually changed.
188
- if (docsEqual ) {
189
- changeSet .addChange (DocumentViewChange .create (Type .METADATA , newDoc ));
190
- } else {
188
+ if (!docsEqual ) {
189
+ if (!shouldWaitForSyncedDocument (oldDoc , newDoc )) {
191
190
changeSet .addChange (DocumentViewChange .create (Type .MODIFIED , newDoc ));
191
+ changeApplied = true ;
192
192
}
193
-
194
193
if (lastDocInLimit != null && query .comparator ().compare (newDoc , lastDocInLimit ) > 0 ) {
195
194
// This doc moved from inside the limit to after the limit. That means there may be some
196
195
// doc in the local cache that's actually less than this one.
197
196
needsRefill = true ;
198
197
}
198
+ } else if (oldDocHadPendingMutations != newDocHasPendingMutations ) {
199
+ changeSet .addChange (DocumentViewChange .create (Type .METADATA , newDoc ));
200
+ changeApplied = true ;
199
201
}
200
202
} else if (oldDoc == null && newDoc != null ) {
201
203
changeSet .addChange (DocumentViewChange .create (Type .ADDED , newDoc ));
204
+ changeApplied = true ;
202
205
} else if (oldDoc != null && newDoc == null ) {
203
206
changeSet .addChange (DocumentViewChange .create (Type .REMOVED , oldDoc ));
207
+ changeApplied = true ;
204
208
if (lastDocInLimit != null ) {
205
209
// A doc was removed from a full limit query. We'll need to requery from the local cache
206
210
// to see if we know about some other doc that should be in the results.
207
211
needsRefill = true ;
208
212
}
209
213
}
214
+
215
+ if (changeApplied ) {
216
+ if (newDoc != null ) {
217
+ newDocumentSet = newDocumentSet .add (newDoc );
218
+ if (newDoc .hasLocalMutations ()) {
219
+ newMutatedKeys = newMutatedKeys .insert (newDoc .getKey ());
220
+ } else {
221
+ newMutatedKeys = newMutatedKeys .remove (newDoc .getKey ());
222
+ }
223
+ } else {
224
+ newDocumentSet = newDocumentSet .remove (key );
225
+ newMutatedKeys = newMutatedKeys .remove (key );
226
+ }
227
+ }
210
228
}
211
229
212
230
if (query .hasLimit ()) {
213
- // TODO: Make QuerySnapshot size be constant time.
214
- while (newDocumentSet .size () > query .getLimit ()) {
231
+ for (long i = newDocumentSet .size () - this .query .getLimit (); i > 0 ; --i ) {
215
232
Document oldDoc = newDocumentSet .getLastDocument ();
216
233
newDocumentSet = newDocumentSet .remove (oldDoc .getKey ());
217
234
newMutatedKeys = newMutatedKeys .remove (oldDoc .getKey ());
@@ -226,6 +243,18 @@ public <D extends MaybeDocument> DocumentChanges computeDocChanges(
226
243
return new DocumentChanges (newDocumentSet , changeSet , newMutatedKeys , needsRefill );
227
244
}
228
245
246
+ private boolean shouldWaitForSyncedDocument (Document oldDoc , Document newDoc ) {
247
+ // We suppress the initial change event for documents that were modified as part of a write
248
+ // acknowledgment (e.g. when the value of a server transform is applied) as Watch will send us
249
+ // the same document again. By suppressing the event, we only raise two user visible events (one
250
+ // with `hasPendingWrites` and the final state of the document) instead of three (one with
251
+ // `hasPendingWrites`, the modified document with `hasPendingWrites` and the final state of the
252
+ // document).
253
+ return (oldDoc .hasLocalMutations ()
254
+ && newDoc .hasCommittedMutations ()
255
+ && !newDoc .hasLocalMutations ());
256
+ }
257
+
229
258
/**
230
259
* Updates the view with the given ViewDocumentChanges and updates limbo docs and sync state from
231
260
* the given (optional) target change.
@@ -273,15 +302,14 @@ public ViewChange applyChanges(DocumentChanges docChanges, TargetChange targetCh
273
302
ViewSnapshot snapshot = null ;
274
303
if (viewChanges .size () != 0 || syncStatedChanged ) {
275
304
boolean fromCache = newSyncState == SyncState .LOCAL ;
276
- boolean hasPendingWrites = !docChanges .mutatedKeys .isEmpty ();
277
305
snapshot =
278
306
new ViewSnapshot (
279
307
query ,
280
308
docChanges .documentSet ,
281
309
oldDocumentSet ,
282
310
viewChanges ,
283
311
fromCache ,
284
- hasPendingWrites ,
312
+ docChanges . mutatedKeys ,
285
313
syncStatedChanged );
286
314
}
287
315
return new ViewChange (snapshot , limboDocumentChanges );
0 commit comments