johncarl81/parceler compartibility request
JackDigital opened this issue · 12 comments
Now about outomatically calling Parcels.wrap/unwrap for objects that are annotated with @parcel?
I'm not big on paceler, see #20 for the discussion (especially towards the end).
I suggest you use instead @AutoParcel which is far more performant and fully composable with icepick.
It is fine, but I use ormlite and gson. I'm really interested in Auto*, but this can't be done with it.
I feel your pain. There's an ongoing discussion with Google to add easy to extend plugins to AutoVaulue so that things like Gson or ORM libraries can integrate their own functionality on top of it. When this solution would be ready it's gonna be a killer app for AutoValue, but as you see we shouldn't hold our breath waiting for a release soon.
Still, this is the right way forward and I'm going to support it.
@frankiesardo, I was curious about your remark that AutoParcel was more performant than Parceler so I did a little performance analysis which you can find here: https://github.com/johncarl81/parceler/tree/master/examples/performance
Here are the numbers I came up with on a real device:
Name | Time to Serialize |
---|---|
Parceler | 404935.1442ns |
Parcelable | 430859.1484ns |
Parceler with AutoValue | 489583.3968ns |
AutoParcel | 1037421.0286ns |
Gson | 1533837.9367ns |
Serializable | 3059908.4384ns |
The numbers show that Parceler and AutoParcel have comparable performance (within a factor of 10) both when dealing with both Parceler and AutoParcel immutable beans (Parceler supports AutoValue) and Parceler mutable beans.
@JackDigital, I went ahead and rebased my previous PR against the latest Icepick. You'll find this version of Icepick that transparently wraps and unwraps Parceler @Parcel
annotated beans here: https://github.com/johncarl81/icepick/tree/parceler_support. If you're interested in a version available from Maven central, let me know.
@johncarl81 thanks for taking the time of setting up this tests, I find the result very interesting. I assumed Parceler
would always be slower because of the level of indirection provided by wrap
and unwrap
, but it turns out the simple strategy of writeObject
adopted by AutoParcel
is the real bottleneck.
But that result is not very realistic for a common usage of an application: you'll never gonna need to write 10000
times the same class, you're more likely to write 10 times 50 different classes and that's where Parceler
would suffer more due to reflection, am I right?
Anyway I don't have anything against Parceler
per se, it's just that I like AutoParcel
more in the way it composes with everything else because it is agnostic: with AutoParcel you actually instantiate a Parcelable object.
Once Google stabilises its AutoValue
API it is likely I'm gonna rewrite the Parcelable code to use types instead of the generic writeObject
. But as you've shown the current speed is quite acceptable.
You're correct, writeObject
(writeValue
?) is a bottleneck and one I've tried to avoid in Parceler. If you look at the source code behind it, it's a series of conditionals, each hitting instanceof
.
I agree this benchmark isn't very realistic, the reason I repeated the tests 10000 times is to get solid averages out of the various techniques.
Serializing different classes (like your example of 10 times for 50 classes) should have similar performance to serializing one class repeatedly. Parceler uses reflection sparingly. Behind the Parceler wrap/unwrap cycle is a generated dictionary class (see Parceler$$Parcels
) that effectively avoids reflection except for the very first lookup call of the dictionary itself. If you'd like to try it out I'd love to hear about the results.
I'd be interested in collaborating on the AutoValue Parcelable plugin, when AutoValue supports that sort of thing. Let me know if you want my input.
@frankiesardo AutoParcel can only be used with immutable objects, I believe. When you do not need a mutable object - it's by far the best scenario.
I find the most important place to use a parcelable object is in a ViewModel
- which will often require it being a mutable object. For example, I have a Quiz view model, in which the active question of the quiz changes as the user progresses through it. AutoParcel will unfortunately not work for this use case, to my knowledge.
Being able to use @Iclicle
on an object is the greatest thing ever. It would be nice to be able to combine that somehow with a mutable object, without having to write the Parcelable
implementation yourself
The information may change, but it's important that the models remain immutable. If you need to capture a Quiz
changing value, then I would create each time a new Quiz
object (and propagate the new model with something like Otto). Uncontrolled mutability causes too many bugs.
going to use VM
to show that an object is a view model.
a QuizVM
is a pretty large object though.
- has a
name
field - an
int currentQuestion
List<QuestionVM>
which each has up to 4AnswerVM
s .
Those AnswerVM
s have:
- a
boolean isCorrect
- a state associated with it (whether the question should reveal that its a correct answer or not or not)
- and whether a specific
AnswerVM
has been selected by the user.
In total, with a single quiz of 10 questions with ~4 possible answers - you are looking at the recreation of up to 40-50 objects every time you change a single value if you want them to remain immutable. To me, it seems mutability is the way to go here, as long as you are careful about how you pass this object across threads. I am actually constructing it initially on a background thread, and then accessing /mutating it only happens on the main thread afterwards.
propagate the new model with something like Otto
What exactly do you mean by this? Sending the construction of this object to a background thread?
Ultimately, something as easy to use as @AutoParcel
for mutable objects would be nice. I would even settle for an IDE plugin that would auto-generate parcelable code based on the class fields.
@ZakTaccardi, there are quite a few options for generating Parcelable
s. In fact there is a plugin along the lines of what you mentioned: https://github.com/mcharmas/android-parcelable-intellij-plugin
@ZakTaccardi If, say, you're adding an Answer
object to an immutable Quiz
model, then what you're creating underneath is:
1 A new Quiz
object. name
and currentQuestion
point to the old ones.
2 A new List<Question>
. All other questions that are not the one you're modifying point to the old ones.
3 A new Question
with everything pointing to the old question except for the Answer
you're adding.
4 The Answer
object.
So you're really just creating 4 objects instead of 1. Your approach makes a lot of sense and mutability if controlled, as in your case, it's temptingly easy. Or, as in the case of animations, it's the only viable performant option. Still, once you start to enforce immutability on more and more areas of your code you'll find it cleaner and easier to understand. It's like passing around Strings
instead of StringBuffers
: you'll never have to worry about somebody changing what's on the other side of the pointer.
If you're interested to explore this route you can have a look at Persistent data structures
that provide efficient structural sharing between immutable collections https://github.com/krukow/clj-ds. Or, yeah, just use the intellij plugin