Approach to serialize cloud endpoint model class to Android file system - android

I have successfully created a cloud endpoint model that allows for easy retrieval of information from App Engine. To reduce the roundtrips and provide a faster user experience, I have identified one instance I wish to store to local storage.
Throughout the rest of my app, I am using ObjectInputStream to read and write the objects such as:
FileInputStream fis = context.openFileInput("PRIVFILE");
ObjectInputStream ois = new ObjectInputStream(fis);
AppModelState s = (AppModelState) ois.readObject();
This obviously requires all data members to implement the Serializable interface. The Model class extends GenericJSON and is not "Serializable", as
public final class ModelClass extends GenericJson {}
I could manually create a serializable object that maps to the model; however, that seems very amateur due to the number of attributes.
The other alternative I considered was creating a Serializable Object wrapper that simply has the JSON string as a member and provides a setter/getter accepting the ModelClass as parameters, such as:
class AppModelState implements Serializable {
private String modelClassJSON;
public ModelClass getModelClass() {
// generate a new ModelClass from the JSON
}
public void setModelClass(ModelClass c) {
// extract the JSON for storage
}
.....
}
I feel like there must be a better way and this should have been solved a dozen times but I am not finding any resources. Please provide input.

I'm doing exactly the same as you say in your question.
Since Cloud Endpoints objects are already serialized for transmit over the wire, they are also serializable to be stored locally. As an added bonus, with Android 3.0 or later, you don't even need to import any libraries -- it's already there! For example:
import com.google.api.client.extensions.android.json.AndroidJsonFactory;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
private static final JsonFactory JSON_FACTORY = new AndroidJsonFactory();
public void putObject(String key, Object value) throws Exception {
byte[] outputbytes = null;
if (value instanceof GenericJson) {
outputbytes = JSON_FACTORY.toByteArray(value);
} else {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ObjectOutputStream objectstream = new ObjectOutputStream(output);
objectstream.writeObject(value);
objectstream.close();
outputbytes = output.toByteArray();
}
// persist "outputbytes" ...
}
public <T> T getObject(String key, Class<T> outclass) throws Exception {
// retrieve saved bytes...
byte[] valuebytes = ...
if (valuebytes[0] == '{' && valuebytes[1] == '"' && valuebytes[valuebytes.length-1] == '}') {
// Looks like JSON...
return JSON_FACTORY.fromString(new String(valuebytes, "UTF-8"), outclass);
} else {
ByteArrayInputStream input = new ByteArrayInputStream(valuebytes);
ObjectInputStream objectstream = new ObjectInputStream(input);
Object value = objectstream.readObject();
objectstream.close();
return outclass.cast(value);
}
}
Note that the default AndroidJsonFactory (as of Android v4.3, anyway) is quite slow when serializing long strings. Create a new JacksonFactory instead if you have performance problems. Everything else stays the same.
Update: If you want to serialize a list of GenericJson objects, you just have to create a GenericJson object that includes a list of those objects. For example:
import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Key;
public static class PersistantJson extends GenericJson {
#Key public int one;
#Key public String two;
}
public static class PersistantJsonList extends GenericJson {
#Key public List<PersistantJson> list = new ArrayList<PersistantJson>();
}
You can now add all your PersistantJson (i.e. some class created by "generate cloud endpoint client library") objects to the .list element of a PersistantJsonList variable and then pass that variable to putObject(). Note that this requires all objects in the list to be of the same class so that deserialization knows what the type is (because JSON serialization does not record the type). If you use List<Object> then what is read back is a List<Map<String, Object>> and you have to extract the fields manually.

I think that doing standard Java serialization of classes that will be used with Endpoints doesn't work very well. The problem is that serialization is binary, and HTTP comm is string.
If you were doing the HTTP comm yourself, rather then using endpoints, I think you would have the same problem. In order to send the object you would serialize it (converting an string members to binary) and then you would have to convert the binary back to string.
So, if the amount of data you are using is not too much, it would probably be easiest to store your objects as JSON.

Related

Can I send custom objects to Android Wear?

I am just learning how to develop for Android Wear, I have created a full screen Activity for Smart Watches and in my mobile part of the application I get some JSON data and create a list of custom objects from this.
On my mobile app I show the information for these objects in a ListView.
On the Wear piece of my application I want to show a limited version of this list, for example the top 3 from the list will be show on my full screen app on the Wearable.
My problem is that there doesn't seem to be a way to send Parcelable Objects to Android Wear, there is no option to putParcelable in the DataItem.
It looks like the only option is to send an object in bytes, like this:
public void sendWearableData(GoogleApiClient aGoogleApiClient, ArrayList<MyObject> myObjectList, String path)
{
googleApiClient = aGoogleApiClient;
byte[] testBytes = new byte[0];
if (googleApiClient.isConnected()) {
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(path);
try {
testBytes = BytesUtils.toByteArray(myObjectList);
} catch (IOException e) {
e.printStackTrace();
}
dataMapRequest.getDataMap().putByteArray(Constants.TEST_KEY, testBytes);
PutDataRequest request = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, request);
}
}
So I have to convert my object to bytes, send it to Android Wear and convert it back? This means that my Objects which I have implemented Parcelable on so I can send them via Intents now also need to implement Serializable, is this correct or is there a better way of doing it?
Bundle-like solution:
In my app I've created a lightweight class, specially for sending it from phone to watch. Because the code is shared between mobile and wearable parts of app it can be easily packed and restored on both devices without code duplication. It provides Bundle-like mechanism, but using DataMap.
Sample implementation:
public class MyObject {
public final long itemId;
public final long sortOrder;
public final String priceString;
public MyObject(long itemId, long sortOrder, String priceString) {
this.itemId = itemId;
this.sortOrder = sortOrder;
this.priceString = priceString;
}
public MyObject(DataMap map) {
this(map.getLong("itemId"),
map.getLong("sortOrder"),
map.getString("priceString")
);
}
public DataMap putToDataMap(DataMap map) {
map.putLong("itemId", itemId);
map.putLong("sortOrder", sortOrder);
map.putString("priceString", priceString);
return map;
}
}
Writing such class will let you consider the what actually needs to be send between devices to send as little as possible. It will also not break when any field will be added or removed (in oppose to the next solution).
Answering to your Parcelable concerns:
If you don't want to write the new class and want to reuse your existing code you can try to use the code below. It will let you stay with only Parcelable interface (without need to implement Serializable interface). I haven't tested it while sending across devices but it successes to marshall() and unmarshall() byte array to/from Parcel and store it in DataMap.
NOTE: I don't know exactly how Google Play Services hold all these DataApi data, but I'm afraid that something may break when such class will be updated.
For example the class will be updated on Android Wear, user will launch the app that would try to read the current data from DataApi (that was "serialized" using old version of this class) and try to read it from byte[] as if it was updated version. These concerns should be tested, but I don't think that they made DataApi so primitive "just because" or to make harder to develop apps on Wear.
I strongly recommend to use Bundle-like solution and to not use the Parcelable solution.
Use this at your own risk.
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.gms.wearable.DataMap;
/**
* <p>Allows to put and get {#link Parcelable} objects into {#link DataMap}</p>
* <b>USAGE:</b>
* <p>
* <b>Store object in DataMap:</b><br/>
* DataMapParcelableUtils.putParcelable(dataMap, "KEY", myParcelableObject);
* </p><p>
* <b>Restore object from DataMap:</b><br/>
* myParcelableObject = DataMapParcelableUtils.getParcelable(dataMap, "KEY", MyParcelableObject.CREATOR);
* </p>
* I do <b>not recommend</b> to use this method - it may fail when the class that implements {#link Parcelable} would be updated. Use it at your own risk.
* #author Maciej Ciemięga
*/
public class DataMapParcelableUtils {
public static void putParcelable(DataMap dataMap, String key, Parcelable parcelable) {
final Parcel parcel = Parcel.obtain();
parcelable.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
dataMap.putByteArray(key, parcel.marshall());
parcel.recycle();
}
public static <T> T getParcelable(DataMap dataMap, String key, Parcelable.Creator<T> creator) {
final byte[] byteArray = dataMap.getByteArray(key);
final Parcel parcel = Parcel.obtain();
parcel.unmarshall(byteArray, 0, byteArray.length);
parcel.setDataPosition(0);
final T object = creator.createFromParcel(parcel);
parcel.recycle();
return object;
}
}
The code is also available on GitHub.

Using Square's Tape library how do i handle calling abstract classes

I'm using Square's Tape library and i've run in to a requirement where i basically need to have an abstract TapeTask class. The problem though is that the deserialization process for the GsonConverter (which implements the library's FileObjectQueue.Converter - as demonstrated in the sample project) doesn't play well with interfaces/abstract classes.
I thought it was a Gson deserialization problem so i registered my Gson instance with a custom TypeAdapter, but that still doesn't do the trick. I figure it has something to do with the FileObjectQueue.Converter.
I'm currently trying to work around this problem, with a nasty wrapper callback interface from my sub-tasks.
My requirement is to have a single TapeQueue and be able to send in multiple types of tasks. So I have a TapeTask abstract class and have concrete implementations like ImageDownloadTask, ImageUploadTask, UrlPostWithRetrofitTask, GoogleAnalyticsTrackerTask ... etc. all going in to a single queue.
Is there a way to achieve this. I guess my question boils down to:
What do i need to do to make the FileObjectQueue.Converter play well with abstract classes?
hint :P : The javadoc for that class says "..you need to also include the concrete class name in the serialized byte array" but i'm not sure what that means. If anyone could post an explanation of how the name can be included in the serialized byte array, in a way that achieves my purpose, i'd be grateful!
I went ahead and wrote an Abstract Gson Convertor. I don't think it's super-efficient but gets the job done:
/**
* Use GSON to serialize classes to a bytes.
*
* This variant of {#link GsonConverter} works with anything you throw at it.
* It is however important for Gson to be able to understand your inner complex objects/entities
* Use an {#link InterfaceAdapter} for these purposes.
*
*/
public class GsonAbstractClassConverter<T>
implements FileObjectQueue.Converter<T> {
public static final String CONCRETE_CLASS_NAME = "concrete_class_name";
public static final String CONCRETE_CLASS_OBJECT = "concrete_class_object";
private final Gson _gson;
public GsonAbstractClassConverter(Gson gson) {
_gson = gson;
}
#Override
public T from(byte[] bytes) {
Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes));
JsonObject completeAbstractClassInfoAsJson = _gson.fromJson(reader, JsonObject.class);
Class<T> clazz;
try {
String className = completeAbstractClassInfoAsJson.get(CONCRETE_CLASS_NAME).getAsString();
clazz = (Class<T>) Class.forName(className);
} catch (ClassNotFoundException e) {
Timber.e(e, "Error while deserializing TapeTask to a concrete class");
return null;
}
String objectDataAsString = completeAbstractClassInfoAsJson.get(CONCRETE_CLASS_OBJECT)
.getAsString();
return _gson.fromJson(objectDataAsString, clazz);
}
#Override
public void toStream(T object, OutputStream bytes) throws IOException {
Writer writer = new OutputStreamWriter(bytes);
JsonObject completeAbstractClassInfoAsJson = new JsonObject();
completeAbstractClassInfoAsJson.addProperty(CONCRETE_CLASS_NAME, object.getClass().getName());
completeAbstractClassInfoAsJson.addProperty(CONCRETE_CLASS_OBJECT, _gson.toJson(object));
_gson.toJson(completeAbstractClassInfoAsJson, writer);
writer.close();
}
}

Custom serializer - deserializer using GSON for a list of BasicNameValuePairs

I'm trying to implement a custom gson serializer/deserialiser for some list of BasicNameValuePair objects.
I saw the partial solution code (for serialization) here:
How do I get Gson to serialize a list of basic name value pairs?
However I wanted to implement also deserialization and I tried my chances and the code is here:
package dto;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.message.BasicNameValuePair;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class KeyValuePairSerializer extends TypeAdapter<List<BasicNameValuePair>> {
#Override
public void write(JsonWriter out, List<BasicNameValuePair> data) throws IOException {
out.beginObject();
for(int i=0; i<data.size();i++){
out.name(data.get(i).getName());
out.value(data.get(i).getValue());
}
out.endObject();
}
#Override
public List<BasicNameValuePair> read(JsonReader in) throws IOException {
ArrayList<BasicNameValuePair> list=new ArrayList<BasicNameValuePair>();
in.beginObject();
while (in.hasNext()) {
String key = in.nextName();
String value = in.nextString();
list.add(new BasicNameValuePair(key,value));
}
in.endObject();
return list;
}
}
Code to initialize and fill the list
ArrayList<BasicNameValuePair> postParameters=new ArrayList<BasicNameValuePair>();
postParameters.add(new BasicNameValuePair("some_key","some_value"));
And here is the code to use the new KeyValuePairSerializer class:
GsonBuilder gsonBuilder= new GsonBuilder();
gsonBuilder.registerTypeAdapter(KeyValuePairSerializer.class, new KeyValuePairSerializer());
Gson gson1=gsonBuilder.create();
//serialization works just fine in the next line
String jsonUpdate=gson1.toJson(postParameters, KeyValuePairSerializer.class);
ArrayList<BasicNameValuePair> postParameters2 = new ArrayList<BasicNameValuePair>();
//postParameters2 = gson1.fromJson(jsonUpdate, KeyValuePairSerializer.class); //? how to cast properly
//deserialization throws an error, it can't cast from ArrayList<BasicNameValuePair> to KeyValuePairSerializer
gson1.fromJson(jsonUpdate, KeyValuePairSerializer.class);
The problem is that it throws an exception at the end and I don't know where exactly is the problem and still not sure how to write the last line to get the result back in the new postParameters2 ArrayList.
Adapted from the GSON Collections Examples:
GsonBuilder gsonBuilder= new GsonBuilder();
gsonBuilder.registerTypeAdapter(KeyValuePairSerializer.class, new KeyValuePairSerializer());
Gson gson1=gsonBuilder.create();
Type collectionType = new TypeToken<ArrayList<BasicNameValuePair>>(){}.getType();
ArrayList<BasicNameValuePair> postParameters2 = gson1.fromJson(jsonUpdate, collectionType);
registerTypeAdapter seems to work only for the serializer not for deserializer.
The only way to call the overridden read function of the KeyValuePairSerializer is to call the:
gson1.fromJson(jsonUpdate, KeyValuePairSerializer.class); without saving the result value in a variable. While it will process the function just fine, it will throw an error inside gson class because it will not be able to cast from the ArrayList to the KeyValuePairSerializer. And I kinda understand why (erasure I guess), just don't know how to do it properly.
Anyway I found a workaround to solve this issue.
It seems that instead of registering the gson object and calling registerTypeAdapter and then using gson1.toJson(Object src, Type typeOfSrc) and gson1.fromJson(String json,Class <T> classOfT) I can get away for deserialization with something simpler like:
KeyValuePairSerializer k= new KeyValuePairSerializer();
parametersList = (ArrayList<BasicNameValuePair>)k.fromJson(jsonUpdate);
Both JsonObject's and NameValuePair's behave in a similar way to dictionaries, I don't think you need to convert one into the other if the use case is similar. Additionally JsonObject allows you to treat your values even easier (instead of looping through the array of value pairs to find the key you need to get its value, JsonObject behaves similarly to a Map in a way that you can directly call the name of the key and it'll return the desired property):
jsonObject.get("your key").getAsString(); (getAsBoolean(), getAsInt(), etc).
For your case I'd create a JsonObject from your string, response or stream and then access it as a map (as shown above):
JsonParser parser = new JsonParser();
JsonObject o = (JsonObject)parser.parse("your json string");
I followed this blog for GSON Collection Examples .
Link is simple to understand and implement.
public class TimeSerializer implements JsonSerializer<time> {
/**
* Implementing the interface JsonSerializer.
* Notice that the the interface has a generic
* type associated with it.
* Because of this we do not have ugly casts in our code.
* {#inheritDoc}
*/
public JsonElement serialize(
final Time time,
final Type type,
final JsonSerializationContext jsonSerializationContext) {
/**
* Returning the reference of JsonPremitive
* which is nothing but a JSONString.
* with value in the format "HH:MM"
*/
return new JsonPrimitive(String.format("%1$02d:%2$02d",
time.getHour(), time.getMinute()));
}

Parsing Picasa API JSON Feed in android

Hi I am trying to implement a simple application showing picasa photos in android.
After many trials now I use the Google Java Api Client (Not the Gdata one), to fetch the Picasa Feed.
In my program, the first layer fields of JSON in the feed (e.g. version and encoding in the code below) can be succesfully mapped, so no problem with the feed and connection
However I am unable to map the second layer fields into Objects.
I use albumFeed = response.parseAs(AlbumFeed.class);
which will invoke the parser.
From the official sample I have this AlbumFeed.java
public class AlbumFeed {
// cannot be List
#Key
List<AlbumEntry> feed;
#Key
String version;
#Key
String encoding;
}
With List <AlbumEntry> feed ;
I will get this Error, which say List cannot be initiate
06-16 14:35:10.789: E/AndroidRuntime(1129): Caused by: java.lang.IllegalArgumentException: unable to create new instance of class java.util.List because it is an interface and because it has no accessible default constructor
I changed to
ArrayList<AlbumEntry> feed
no error will be shown but the ArrayList will be empty. Can anyone advise me the correct Way to map JSON to Objects using the given JsonObjectParser?
Many thanks if anyone can answer my question
more CODE
public class newApiActivity extends Activity {
HttpTransport transport = AndroidHttp.newCompatibleTransport();
static final JsonFactory JSON_FACTORY = new JacksonFactory();
public static HttpRequestFactory createRequestFactory(HttpTransport transport) {
return transport.createRequestFactory(new HttpRequestInitializer() {
public void initialize(HttpRequest request) {
// final JsonCParser jsoncparser = new JsonCParser(JSON_FACTORY);
request.setParser(new JsonObjectParser(GSON_FACTORY));
// request.setParser(jsoncparser);
GoogleHeaders headers = new GoogleHeaders();
headers.setApplicationName("APOD/1.0");
headers.setGDataVersion("2");
request.setHeaders(headers);
}
});
}
public void mainLogic(){
HttpRequest buildGetRequest = reqFactory.buildGetRequest(url);
HttpResponse response = buildGetRequest.execute();
albumFeed = response.parseAs(AlbumFeed.class);
}
}
where AlbumEntry.java will be something like
I am taking the xmlns to test the behavior)
public class AlbumEntry extends Entry {
#Key("xmlns")
public String xmlns;
}
The JSON Feed itself is like:
{"version":"1.0","encoding":"UTF-8",
"feed":{"xmlns":"http://www.w3.org/2005/Atom","xmlns$gphoto":"http://schemas.google.com/photos/2007,……}}
Stupid me. There is nothing to do with the android/ picasa api context.
After reading more on the GSON documentation, according to the json object {} will be a map but not an array . While array can be mapped to List, map can be mapped into an Object or Map
I changed to simple
#Key
AlbumEntry feed;
in AlbumFeed.java
It starts working.
A nice reference on GSON is here
Converting JSON to Java

Question about serializing a Location subclass in order to write them to a file

As I understand things, in order to save an ArrayList of objects to a file using ObjectOutputStream, the Objects in the ArrayList need to implement Serializable.
I'm trying to save an ArrayList of Locations to a file, so I extend Location into a class that does that (and nothing else). So I have a class that looks like...
import java.io.Serializable;
import android.location.Location;
public class MyLocation extends Location implements Serializable{
/**
*
*/
private static final long serialVersionUID = 7599200646859829149L;
public MyLocation(Location l) {
super(l);
}
}
But now, in my location listener it crashes at
public void onLocationChanged(Location loc) {
locat = (MyLocation)loc;
I suppose it doesn't like the typecasting? I dunno, I'm a little confused. Sorry if this is obvious, I haven't been doing this long.
The serialVersionUID line was generated by Eclipse using "add generated serial version ID"
Update:
Like I said before, I'm trying to read/write an arraylist of Locations to/from a file. When I write my data to a file, I don't get any exceptions, but I do get an exception when I try to read it. Basically I have a class that extends ArrayList (because I needed it to be parseable for Listview purposes...
public class LocationList extends ArrayList<MyLocation> implements Parcelable {
private static final long serialVersionUID = 1L;
it's a LocationList named waypoints that I write to a file. This seems to work fine (ie it doesn't throw an exception or crash)...
FileOutputStream fos;
try {
fos = context.openFileOutput(fileName, Context.MODE_PRIVATE);
ObjectOutputStream os;
os = new ObjectOutputStream(fos);
os.writeObject(waypoints); // <-- writeObject doesn't throw an exception.
os.close();
but when I try to read that file, I get an IO exception...
FileInputStream fis;
try {
fis = context.openFileInput(fileName);
ObjectInputStream is = new ObjectInputStream(fis);
is.readObject(); // <--this is the line that throws an IO exception
is.close();
This is probably a naive attempt at saving/loading a moderately complicated object. Any advice?
NOTE: I simplified it a bit. Originally the offending line was
waypoints = (LocationList) is.readObject();
but I was worried that I had a similar problem with the casting as above so i took it out temporarily. Anyway, readObject() throws an exception without the typecast there, so I guess that's not my current problem.
Is the location object that is passed to you a MyLocation instance? My guess would be no, since the system is providing it. Just because you extend a class doesn't mean you can cast from the super type. What you really want to do is:
locat = new MyLocation(loc);

Categories

Resources