@@ -386,8 +386,10 @@ public String toString() {
386
386
private final ParseMulticastDelegate <ParseObject > saveEvent = new ParseMulticastDelegate <>();
387
387
388
388
/* package */ boolean isDeleted ;
389
+ /* package */ boolean isDeleting ; // Since delete ops are queued, we don't need a counter.
389
390
//TODO (grantland): Derive this off the EventuallyPins as opposed to +/- count.
390
391
/* package */ int isDeletingEventually ;
392
+ private boolean ldsEnabledWhenParceling ;
391
393
392
394
private static final ThreadLocal <String > isCreatingPointerForObjectId =
393
395
new ThreadLocal <String >() {
@@ -2158,6 +2160,7 @@ private Task<Void> deleteAsync(final String sessionToken, Task<Void> toAwait) {
2158
2160
return toAwait .onSuccessTask (new Continuation <Void , Task <Void >>() {
2159
2161
@ Override
2160
2162
public Task <Void > then (Task <Void > task ) throws Exception {
2163
+ isDeleting = true ;
2161
2164
if (state .objectId () == null ) {
2162
2165
return task .cast (); // no reason to call delete since it doesn't exist
2163
2166
}
@@ -2168,6 +2171,12 @@ public Task<Void> then(Task<Void> task) throws Exception {
2168
2171
public Task <Void > then (Task <Void > task ) throws Exception {
2169
2172
return handleDeleteResultAsync ();
2170
2173
}
2174
+ }).continueWith (new Continuation <Void , Void >() {
2175
+ @ Override
2176
+ public Void then (Task <Void > task ) throws Exception {
2177
+ isDeleting = false ;
2178
+ return null ;
2179
+ }
2171
2180
});
2172
2181
}
2173
2182
@@ -4237,7 +4246,26 @@ public void writeToParcel(Parcel dest, int flags) {
4237
4246
4238
4247
/* package */ void writeToParcel (Parcel dest , ParseParcelEncoder encoder ) {
4239
4248
synchronized (mutex ) {
4240
- // Write className and id regardless of state.
4249
+ // Developer warnings.
4250
+ ldsEnabledWhenParceling = Parse .isLocalDatastoreEnabled ();
4251
+ boolean saving = hasOutstandingOperations ();
4252
+ boolean deleting = isDeleting || isDeletingEventually > 0 ;
4253
+ if (saving ) {
4254
+ Log .w (TAG , "About to parcel a ParseObject while a save / saveEventually operation is " +
4255
+ "going on. If recovered from LDS, the unparceled object will be internally updated when " +
4256
+ "these tasks end. If not, it will act as if these tasks have failed. This means that " +
4257
+ "the subsequent call to save() will update again the same keys, and this is dangerous " +
4258
+ "for certain operations, like increment(). To avoid inconsistencies, wait for operations " +
4259
+ "to end before parceling." );
4260
+ }
4261
+ if (deleting ) {
4262
+ Log .w (TAG , "About to parcel a ParseObject while a delete / deleteEventually operation is " +
4263
+ "going on. If recovered from LDS, the unparceled object will be internally updated when " +
4264
+ "these tasks end. If not, it will assume it's not deleted, and might incorrectly " +
4265
+ "return false for isDirty(). To avoid inconsistencies, wait for operations to end " +
4266
+ "before parceling." );
4267
+ }
4268
+ // Write className and id first, regardless of state.
4241
4269
dest .writeString (getClassName ());
4242
4270
String objectId = getObjectId ();
4243
4271
dest .writeByte (objectId != null ? (byte ) 1 : 0 );
@@ -4247,26 +4275,23 @@ public void writeToParcel(Parcel dest, int flags) {
4247
4275
dest .writeByte (localId != null ? (byte ) 1 : 0 );
4248
4276
if (localId != null ) dest .writeString (localId );
4249
4277
dest .writeByte (isDeleted ? (byte ) 1 : 0 );
4250
- // Squash the operations queue if needed .
4278
+ // Care about dirty changes and ongoing tasks .
4251
4279
ParseOperationSet set ;
4252
- if (hasOutstandingOperations ()) { // There's more than one set.
4253
- Log .w (TAG , "About to parcel a ParseObject while a save / saveEventually operation is " +
4254
- "going on. The unparceled object will act as if these tasks had failed. This " +
4255
- "means that the subsequent call to save() will update again the same keys. " +
4256
- "This is dangerous for certain operations, like increment() and decrement(). " +
4257
- "To avoid inconsistencies, wait for save operations to end before parceling." );
4258
- ListIterator <ParseOperationSet > iterator = operationSetQueue .listIterator ();
4280
+ if (hasOutstandingOperations ()) {
4281
+ // There's more than one set. Squash the queue, creating copies
4282
+ // to preserve the original queue when LDS is enabled.
4259
4283
set = new ParseOperationSet ();
4260
- while ( iterator . hasNext () ) {
4261
- ParseOperationSet other = iterator . next ( );
4262
- other .mergeFrom (set );
4263
- set = other ;
4284
+ for ( ParseOperationSet operationSet : operationSetQueue ) {
4285
+ ParseOperationSet copy = new ParseOperationSet ( operationSet );
4286
+ copy .mergeFrom (set );
4287
+ set = copy ;
4264
4288
}
4265
4289
} else {
4266
4290
set = operationSetQueue .getLast ();
4267
4291
}
4268
4292
set .setIsSaveEventually (false );
4269
4293
set .toParcel (dest , encoder );
4294
+ // Pass a Bundle to subclasses.
4270
4295
Bundle bundle = new Bundle ();
4271
4296
onSaveInstanceState (bundle );
4272
4297
dest .writeBundle (bundle );
@@ -4286,28 +4311,31 @@ public ParseObject[] newArray(int size) {
4286
4311
};
4287
4312
4288
4313
/* package */ static ParseObject createFromParcel (Parcel source , ParseParcelDecoder decoder ) {
4289
- ParseObject obj ;
4290
4314
String className = source .readString ();
4291
- if (source .readByte () == 1 ) { // We have an objectId.
4292
- obj = createWithoutData (className , source .readString ());
4293
- } else {
4294
- obj = create (className );
4295
- }
4315
+ String objectId = source .readByte () == 1 ? source .readString () : null ;
4316
+ // Create empty object (might be the same instance if LDS is enabled)
4317
+ // and pass to decoder before unparceling child objects in State
4318
+ ParseObject object = createWithoutData (className , objectId );
4296
4319
if (decoder instanceof ParseObjectParcelDecoder ) {
4297
- ((ParseObjectParcelDecoder ) decoder ).addKnownObject (obj );
4298
- }
4299
- State state = State .createFromParcel (source , decoder ); // Returns ParseUser.State if needed
4300
- obj .setState (state ); // This calls rebuildEstimatedData
4301
- if (source .readByte () == 1 ) obj .localId = source .readString ();
4302
- if (source .readByte () == 1 ) obj .isDeleted = true ;
4320
+ ((ParseObjectParcelDecoder ) decoder ).addKnownObject (object );
4321
+ }
4322
+ State state = State .createFromParcel (source , decoder );
4323
+ object .setState (state );
4324
+ if (source .readByte () == 1 ) object .localId = source .readString ();
4325
+ if (source .readByte () == 1 ) object .isDeleted = true ;
4326
+ // If object.ldsEnabledWhenParceling is true, we got this from OfflineStore.
4327
+ // There is no need to restore operations in that case.
4328
+ boolean restoreOperations = !object .ldsEnabledWhenParceling ;
4303
4329
ParseOperationSet set = ParseOperationSet .fromParcel (source , decoder );
4304
- for (String key : set .keySet ()) {
4305
- ParseFieldOperation op = set .get (key );
4306
- obj .performOperation (key , op ); // Update ops and estimatedData
4330
+ if (restoreOperations ) {
4331
+ for (String key : set .keySet ()) {
4332
+ ParseFieldOperation op = set .get (key );
4333
+ object .performOperation (key , op ); // Update ops and estimatedData
4334
+ }
4307
4335
}
4308
4336
Bundle bundle = source .readBundle (ParseObject .class .getClassLoader ());
4309
- obj .onRestoreInstanceState (bundle );
4310
- return obj ;
4337
+ object .onRestoreInstanceState (bundle );
4338
+ return object ;
4311
4339
}
4312
4340
4313
4341
/**
0 commit comments