Why is dataSnapshot deserialized in ViewModel and not in class extending LiveData? - firebase

I read these blog posts written by Doug Stevenson Part 1, Part 2, Part 3. On part 2 he says that
The Realtime Database SDK makes it really easy to convert a DataSnapshot into a JavaBean style object
And mentions that we can use this line of code to get the DataSnapshot deserialized into a HotStock object (a JavaBean class)
HotStock stock = dataSnapshot.getValue(HotStock.class)
I´m confused because he first uses Transformation.map and then he says that if a LiveData transformation is expensive we can create a MediatorLiveData object from the ViewModel to get the DataSnapshot converted into a HotStock object.
My question is: Why can´t we just call the
HotStock stock = dataSnapshot.getValue(HotStock.class)
from the onDataChange() method on the ValueEventListener which resides in the class extending the LiveData super class and then simply use the setValue() method to pass the HotStock object directly to the ViewModel and then to the Activity or Fragment observing for changes to the LiveData?

You can do that, but getValue() passing a class object is actually kind of slow (especially the first time, for a particular class) because the SDK has to use reflection to figure out how to map all the fields into the object it creates. Java reflection known to be fairly slow. But it's up to you what you want to use.

Related

Converting a JsonDocument to its mapped entity

I use CouchbaseRepository in my project but sometimes I use lower level couchbase sdk methods to retrieve JsonDocument. Is there a way I can use spring-data-couchbase to convert a JsonDocument to lets say a User?
This is all done internally in CouchbaseTemplate in the method private <T> T mapToEntity(String id, Document<String> data, Class<T> entityClass)
But as you can see it's private so I can't call it myself.
You can create a wrapper which uses Jackson to convert the JsonDocument to any object you want.
mapper.readValue(doc.content().toString(), SomeClass.class);
The first argument is the JsonDocument, the content method is what contains the actual Json.

Saving an entire one-to-many structure of transient objects in one query

In Short
I seem to have landed on a MAJOR anti-pattern of saving objects WAY too many times. I've read through the limited Objectify docs and can't seem to find the right pattern to use.
Details
I have multiple objects I want to store. They are all transient (they don't exist in the database yet) and they have a one-to-many relationship. I don't want to sit and call ofy().save() on every last object in my hierarchy.
In the following example, a Player has a List of Cards.
My Model:
#Entity
public class Player {
#Id private Long id = null;//will be generated
private List<Ref<Card>> cards = new ArrayList<Ref<Card>>();
//getters and setters here
}
public class Card{
#Id private Long id = null;//will be generated
//lots of other fields and getters and setters here
}
My Operation:
I need to create a new player and new card, with the player having a reference to the card in his List "cards."
IDEAL SOLUTION:
I would like to just create the player and card java objects, set their relationships, and pass them to Objectify to be saved. Like this:
Player player = new Player();
Card card = new Card();
player.setPlayer(Ref.create(card));
ofy.save().entity(player).now();
That will fail. The 3rd line attempts to create a new Ref for Card, which cannot be done because Card doesn't have an Id yet, which will be assigned to it once it's already persisted. It seems I must never associate an object with another until one has already been saved.
Current Crappy Solution
So, my solution must be to save the Card first, and then relate it to the Player, then save the player.
Player player = new Player();
Card card = new Card();
ofy().save().entity(card).now();
player.setPlayer(Ref.create(card));
ofy().save().entity(card).now();
This is insane. It seems reasonable at first, but my app is dealing with many more relationships than just this, and with this pattern my algorithm will be a spiderweb of checking for transient objects inside collections before saving the entity I'm actually concerned with.
There MUST be some way to tell Objectify to just SAVE all child/related entities along with the entity I've requested, and furthermore generate the Ids necessary instead of throwing an Exception at me.
Furthermore, I'll also need this sort of "recursive save" solution even when none of my objects are transient (ie they all have IDs already). I can't waste my time iterating through collections and then all the collections WITHIN those collections and saving them all. I'm going to need some way of telling Objectify to just SAVE THIS WHOLE HEIRARCHY OF OBJECTS I just passed you.
I've been reading around this #Load annotation and I feel like maybe there's something in there I'm missing... I don't know. Need help. Documentation is slim.
UPDATED SOLUTION
For posterity -
Using the allocateId() method decouples the entire ID generation constraint away from the database and you get a VERY clean pattern, particularly if you do as I did:
All database #Entity classes get a private constructor and a static public factory for creating transient objects. This static factory method ( createTransient() ) will always allocate a new ID. So then, all client code can use this method for acquiring new transient objects, or the obvious objectify load for acquiring existing persisted instances. Simple. Done. Lovely.
I recommend two things:
Allocate ids manually when you construct your objects using ObjectifyFactory.allocateId(). Do not use the "save with null autogenerates" feature. As you've noticed, it's a PITA to deal with entity objects that have null ids, so don't allow them to exist.
Use deferred saves. ofy().defer().save().entity(blah); You can save almost any number of things this way and they'll only get saved once on commit (or closing of the objectify session). Deferring save on the same entity multiple times produces only a single save.
This pattern of leaving ids null and filling it in on save is a holdover from the JPA days. It didn't work very well with JPA either; there were plenty of frustrating edge cases dealing with entities missing ids (especially when you wanted to put the in maps or sets). The best solution is to simply guarantee that no entity is ever missing an id in the first place.
Note that you'll want to allocate the id in a custom constructor, not the no-args constructor that Objectify uses to build your entity on load. Allocating an id is cheap but still a call to the GAE service layer and you don't want to do this on every load.

When Should I Call Write in Realm

If I call write and update an object with a list of newly created other objects, will those objects automatically be written into Realm as well?
If I understand your question correctly, you will update a Realm Object inside a write transaction and that object has a one-to-many relationship to another Realm Object. Your class may look like this for example:
public class Owner: Object {
let listOfObjects: List<SomeObject> = List<SomeObject>()
}
Then if you create a list of type SomeObject in a write block and set your class' listOfObjects to the newly created list, the new list of objects will be persisted in Realm when the write block ends. (Assuming your SomeObject class is a subclass of Object ofcourse)
From my understanding (in Realm Objective-C and Realm Swift at least), no. If you create a new list of objects that aren't persisted in Realm yet and assign them as child objects of an object that is, they still won't be guaranteed to be added to Realm that way.
For best practice, I recommend that in your write transaction, you both explicitly add the new objects to Realm and then add them to the child object list of that object.

is it possible to get the Entity object before objectify saves it (and fill some other data)

I have some personal data structure mixed with "standard fields". I would like to avoid the manual work on simple fields (with datastore native API):
toPersist.setProperty("field1", value1);
toPersist.setUnindexedProperty("field2", value2);
but I still want to get the prefilled Entity instance toPersist so I can add my own #Ignore fields my self
For example:
Entity filled = OfyService.ofy().save().entity(this).fill();
filled.setProperty("mySpecialField", jsonValue);
//...
// I want to save my entities alone
datastore.put( filled );
reversely I'd like to get the Entity object representing each entry in a load() call.
Is this possible? or do I have to dive into Objectify code to hack it?
thanks for your answers
I don't follow your question exactly, but I'm pretty sure what you're looking for are the #OnLoad and #OnSave annotations. You add them to methods within your entity classes, and those methods will be called just after an entity is loaded, or just before one is saved, respectively. The documentation for them is here.
Edit:
After your comments (below) I now understand what you are trying to accomplish. Yes, Objectify supports this (though I have never tried it myself). You want to use the Saver.toEntity() and Loader.fromEntity() methods. It appears you can use them like this:
// Use Objectify to convert a POJO into an Entity
Entity filled = ofy().save().toEntity(myPojo);
// Use Objectify to convert an Entity into a POJO
Object pojoCopy = ofy().load().fromEntity(filled);

Create key with objectify

Calling Objectify method:
Key.create(ParentClass.class parentClass, KindClass.class kindClass, String id)
for the exactly same parameters twice at the same time, I got:
name=UWxoCs7KpxDu2fYBI3s2fkOq-wM
name=jOqQzhZzAScJ0O3OEtYF3jzb34Q
Does this method need to run in a transaction so I get a predictable result?
The app id and namespace are also encoded in the key. You can see the components that represent a stable key here:
https://code.google.com/p/googleappengine/source/browse/trunk/java/src/main/com/google/appengine/api/datastore/Key.java
I cannot find any reference to an Objectify Key.createKey method. Which version of Objectify are you using?
There is however a Key.create(Key<?> parent, java.lang.Class<? extends T> kindClass, java.lang.String name), trying using that and let us know how you get on.
Here is the API page for Key https://objectify-appengine.googlecode.com/svn/trunk/javadoc/com/googlecode/objectify/Key.html

Resources