-
-
Notifications
You must be signed in to change notification settings - Fork 735
Parcelable object #625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parcelable object #625
Conversation
An issue I can see (off the top of my head) is that in special occasions we might stack overflow, for example However if this is an issue, it is already addressed by the SDK, because this will be true also with JSON encoding / decoding that the SDK already does for caching. So we should just see how it’s addressed in that case, and emulate the behavior. I think it’s about subclassing |
@@ -94,6 +98,14 @@ private static ParseObjectSubclassingController getSubclassingController() { | |||
return new Builder(className); | |||
} | |||
|
|||
/* package */ static State createFromParcel(Parcel source) { | |||
String className = source.readString(); | |||
if ("_User".equals(className)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we have this constant defined somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it’s left as _User everywhere, e.g. a few lines above.
protected void onSaveInstanceState(Bundle outState) { | ||
super.onSaveInstanceState(outState); | ||
synchronized (mutex) { | ||
outState.putBoolean("_isCurrentUser", isCurrentUser); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, should be a constant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I’ll fix this
Are you talking about some type of recursive put? Yeah there should be a check to make sure you're not putting an object against yourself. I don't think Parcelable changes that fact (or might exacerbate it?) In general I think the challenge with supporting this functionality is going to be all the inflight operations that occur. I think the relative mechanics of implementing a parcelable interface is a lot of plumbing work. We just have to make sure we have in the docs about best practices with this stuff. |
Well not a recursive put, but recursive parceling because we parcel all dirty data for all objects, even child objects. The recursion might also be more hidden, e.g.
I wrote a similar test and it throws |
@natario1 updated the pull request - view changes |
@natario1 updated the pull request - view changes |
@natario1 updated the pull request - view changes |
Codecov Report
@@ Coverage Diff @@
## master #625 +/- ##
===========================================
+ Coverage 51.05% 52.4% +1.35%
- Complexity 1524 1647 +123
===========================================
Files 127 131 +4
Lines 9699 10096 +397
Branches 1310 1404 +94
===========================================
+ Hits 4952 5291 +339
- Misses 4345 4364 +19
- Partials 402 441 +39
Continue to review full report at Codecov.
|
boolean saving = hasOutstandingOperations(); | ||
boolean deleting = isDeleting || isDeletingEventually > 0; | ||
if (saving) { | ||
Log.w(TAG, "About to parcel a ParseObject while a save / saveEventually operation is " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will it be easy to trigger these issues if people are navigating between activities or you have a lot of rotation-type changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess depends on developer habits, but generally this should be rare (a configuration change while the object is in the middle of a save).
I agree the message is too long, but I could not make it shorter... Maybe if we merge this and update docs, we can replace with About to parcel a ParseObject with ongoing tasks. This might be dangerous. Read <link to docs>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do that later. Would rather have a really big warning msg when this thing is first being used rather than a one-liner so we get some feedback about whether it happens a lot or not a ton.
@wangmengyan95 @grantland any thoughts? |
I haven't had time to go into the code in-depth, but the description seems like this has been well thought out. I haven't touched Parse code in about a year now, but the only thing that jumped out to me originally was the single instance use case for LDS which seems to have been covered. |
Glad to hear that @grantland , thank you for your time. |
rebase =) |
94945f0
to
b0d07c2
Compare
7ab3f11
to
a16971a
Compare
rebase again |
a16971a
to
e06280a
Compare
@rogerhu done now |
This is a working stub that implements
Parcelable
interface intoParseObject
( #122 ).ParseACL
ParseRelation
ParseObject
. It parcels both server data and dirty data. Internally this is done by parceling theParseObject.State
for server data, and theParseOperationSet
s attached to the object. These sets are squashed into a singleParseOperationSet
, as outlined by grantland in ParseObject implements Parcelable #122 , and the merged set is saved to parcel.ParseParcelEncoder
andParseParcelDecoder
, utility classes that help parceling when we host untyped lists (e.g.Map<String, Object>
). They work by saving a type string into the parcel and checking that when unparceling. Eventually these classes will be extended to work forParseQuery
as well.ParseObject
subclasses. They do not need to provide aCREATOR
static field. Instead, we have two new protected methods inParseObject
which should be familiar to any developer,onSaveInstanceState(Bundle)
andonRestoreInstanceState(Bundle)
. For example, they are used inParseUser
to keep theisCurrentUser
boolean. We pass a Bundle because that’s much safer than aParcel
.ParsePin
,ParseInstallation
,ParseSession
,ParseRole
,EventuallyPin
and finallyParseUser
ParseUser
since it has an internalState
class and own fields (isNew, isCurrentUser).Log.w
) if the object about to be parceled has ongoing save tasks / scheduled saveEventually tasks, to make him aware of the implications outlined by grantland in ParseObject implements Parcelable #122 .Log.w
) if the object has ongoing delete tasks / scheduled deleteEventually tasks, as aboveApproach
My approach was to never give
Parcelable
to inner classes (e.g.ParseObject.State
,ParseACL.Permissions
) even if they need to be parceled. That is because unparceling requires theCREATOR
field to be publicly accessible, and we don’t want to makeParseObject.State
public.Instead, simple methods like writeToParcel or constructor(Parcel) are implemented to inner classes, and are called from the outer public class. Internally this ensures also inheritance when needed (e.g.
ParseUser.State
that extendsParseObject.State
). We useParcelable
only when the developer would benefit from it.Consistency
I built this trying to keep consistency with JSON encoding / decoding that is already built in into classes. For example
ParseParcelDecoder
/Encoder
works just likeParseDecoder
/Encoder
for JSONParseOperationSet
hastoRest(ParseEncoder)
andfromRest(JSONObject, ParseDecoder)
. AddedtoParcel(Parcel, ParseParcelEncoder)
andfromParcel(Parcel, ParseParcelDecoder)
ParseFieldOperation
interface hasencode(ParseEncoder)
and nowencode(Parcel, ParseParcelEncoder)
ParseFieldOperationFactory
for decodingRecursiveness
This now avoids
StackOverflowErrors
caused by nested ParseObject in the object tree. It’s pretty common for aParseObject
to be self referenced by one of its keys. This can be explicit (e.g.object.put(“itself”, object)
or more obscure (e.g.ParseRelation
is parceled when the parent object is parceled, but the relation itself holds a reference to the parent...).This is solved by using stateful encoders / decoders called
ParseObjectParcelEncoder
andParseObjectParcelDecoder
. When the encoder traverses the objects / keys tree,Later, when the decoder parses the parcel,
This is pretty much what
KnownParseObjectDecoder
is already doing for JSON decoding, so there is still consistency with JSON stuff.The required encoder / decoder instance is passed to every class so it can safely keep trace of actions.
Ongoing tasks and LDS
By default this follows what was told in #122, that is, if there are ongoing save / save eventually tasks and we parcel, the tasks might succeed or fail but the unparceled object will act as if they failed.
The operation set queue is squashed to a single
ParseOperationSet
, that is parceled and restored, and contains the dirty changes from the last successful save at the moment of parceling.If local datastore is enabled and we manage to get the object from LDS memory when unparceling, we can have better behavior because the same instance is restored. In this case the original operation set queue is kept safe, and the unparceled object will be internally updated when save / delete tasks end.
What do you think of this approach?
I think there’s not much missing anymore.