androidgoogle-cloud-datastoregoogle-cloud-endpoints

Sending and retrieving data from datastore with mobile backend starter


I'm trying to use Mobile Backend Starter in my Android application. In order to do that I need to store some data in the Datastore.

I'm using the provided object CloudEntity but I can only consistently insert and read String.

That's the example code I used to send data:

CloudEntity entity = new CloudEntity(TEST_KIND_NAME);

entity.put(KEY_DATE, new Date(System.currentTimeMillis()));
entity.put(KEY_CALENDAR, Calendar.getInstance());
entity.put(KEY_LONG,  Long.valueOf(Long.MAX_VALUE));                
entity.put(KEY_INTEGER, Integer.valueOf(Integer.MAX_VALUE));
getCloudBackend().insert(entity, simpleHandler);

and this is how I read the data back (next code goes in the onComplete in the CloudBackendHandler:

StringBuffer strBuff = new StringBuffer();
strBuff.append("Inserted: \n");
strBuff.append("\tId = " + result.getId() + "\n");
Object o;
o = result.get(KEY_DATE);            
strBuff.append("\tDate was retrieved as : " + ((o == null)? "null" : o.getClass().getName()) + "\n");
        
o = result.get(KEY_CALENDAR);
strBuff.append("\tCalendar was retrieved as : " + ((o == null)? "null" : o.getClass().getName()) + "\n");
        
o = result.get(KEY_LONG);
strBuff.append("\tLong was retrieved as : " + ((o == null)? "null" : o.getClass().getName()) + "\n");
        
o = result.get(KEY_INTEGER);
strBuff.append("\tInteger was retrieved as : " + ((o == null)? "null" : o.getClass().getName()) + "\n");
        
o = result.get(KEY_BOOLEAN);
strBuff.append("\tBoolean was retrieved as : " + ((o == null)? "null" : o.getClass().getName()) + "\n");
mTvInfo.setText(strBuff);

And what I get as result is:

Data inserted as Date and Calendar returns null.

Data inserted as Integer returns BigDecimal.

Data inserted as Longreturns a String.

My question is: Can I send (and read back) other data than `String? And if so. How?


Solution

  • After some time experimenting with the Android Mobile Backed Starter I found a link to a "sort of" (very limited) documentation: Mobile Backend Starter.

    What I found is that if you send an Integer (and if I trust de documentation a Float or a Double) it is stored in the DataStore as a numeric field. And is returned as a BigDecimal when you send a query (through ClouldQuery).

    Nevertheless, if you pass a Long as a property in your CloudEntity, it will be stored as a String in the DataStore and returned as such. And this is not trivial, as String fields has limitations on allowed comparisons.

    If you send a DateTime, the documentation tells you that you will get back a String, but don't tells you that it will be stored in the DataStore as a String too.

    This is important because you can't do all the comparisons with Strings. It is only allowed the equality (and ne) comparison (you can't test a greater than filter over a String property in your queries). So you can't store timestamps as Long (will be converted to String and you won't be able to compare them) and you can't set timestamps as DateTime for the same reason. And you just can't store Date nor Calendar objects.

    Anyway every CloudEntity has two DateTime porperties by default CloudEntity.PROP_CREATED_AT and CloudEntity.PROP_UPDATED_AT. You can set a query filter with this fields. To do so you need to set the filter as

    CloudQuery myQuery = new CloudQuery(<your kind name>);
    myQuery.set<things>...
    DateTime dateTime = new DateTime(<the time you want>);
    myQuery.setFilter(F.gt(CloudEntity.PROP_UPDATED_AT, dateTime));
    

    You need to use a DateTime for the comparison. If you are courious, you can NOT use Date instead of DateTime for this comparation. You would get this error:

    com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
    {
        "code": 400,
        "errors": [
        {
           "domain": "global",
           "message": "java.lang.IllegalArgumentException: _updatedAt:java.util.LinkedHashMap is not a supported property type.",
           "reason": "badRequest"
        }
        ],
        "message": "java.lang.IllegalArgumentException: _updatedAt: java.util.LinkedHashMap is not a supported property type."
    ...
    

    Other weird thing is that, aparently, you can not do comparisons with dateTime = new DateTime(0) (i.e. 1970-01-01T01:00:00.000+01:00) as the backend receives the query as:

    query: (_kindName:"Data") AND ( _updatedAt > "1970-01-01T01:00:00.000+01:00" ), schema: {_kindName=STRING, _updatedAt=STRING}
    

    Won't give any error but will return nothing list: result: null. Looks like it treats the comparison as a String. If you use DateTime dateTime = new DateTime(1) instead, you will send a query as:

    list: executing query: {filterDto={operator=GT, values=[_updatedAt, 1970-01-01T01:00:00.001+01:00]},
    

    looks the same as before but the backend will execute the query as:

     query: (_kindName:"Data") AND ( _updatedAt > 1 ), schema: {_kindName=STRING, _updatedAt=DOUBLE}
    

    As I see a DOUBLE I tried submit a Double instead a DateTime, but it doesn't work (no error but not result).

    But, GOOD NEWS EVERYONE! We can do the comparison with an Integer as:

    Integer anInteger = Integer.valueOf(0);
    myQuery.setFilter(F.gt(CloudEntity.PROP_UPDATED_AT, anInteger));
    

    the backend sees:

    query: (_kindName:"Data") AND ( _updatedAt > 0 ), schema: {_kindName=STRING, _updatedAt=INT32}
    

    and we'll get the expected value (all the entities updated since 1970-01-01T01:00:00.000+01:00)

    Sorry for my mistakes (my english isn't good) and I hope this can help someone and, at least, save him some time.