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

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();
}
}

Related

Serializing list of Files with gson not working in Android 12+ (API 32, 33) [duplicate]

After updating the targetSdk to 31, gson.ToJson started giving empty results for List<File> on android 12 device (vivo v2036). Tried passing TypeToken as well still remains the same. Funny thing is that its working fine on lower androids and on targetSdk 30.
public void save(Context context, List<File> files) {
Gson gson = new Gson();
String json = gson.toJson(files);
//getting json value as "[{}]"
}
Gson has no built-in adapter for java.io.File so it falls back to using reflection. This should be avoided for third-party classes because it accesses their internal fields which makes you dependent on their internal implementation. That internal implementation could change at any point because it is not part of the public API, and can therefore break your JSON serialization logic.
As mentioned by #CommonsWare you can either change the type of the list to List<String> and by doing so only use types for which Gson has built-in adapters. Or you can also solve this by registering a custom TypeAdapter for File:
class FileTypeAdapter extends TypeAdapter<File> {
#Override
public void write(JsonWriter out, File file) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(file.getPath());
}
}
#Override
public File read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
} else {
return new File(in.nextString());
}
}
}
(Coincidentally this custom TypeAdapter also produces more compact JSON because it serializes the File as a JSON string value whereas the reflection-based adapter would serialize it as JSON object with JSON string property.)
You can then register the adapter like this:
Gson gson = new GsonBuilder()
.registerTypeAdapter(File.class, new FileTypeAdapter())
.create();
If that does not solve your issue, you could also try registering the adapter with GsonBuilder.registerTypeHierarchyAdapter. Possibly Android is creating instances of custom File subclasses.
To detect any cases where you accidentally depend on reflection-based serialization for Android classes you can use GsonBuilder.addReflectionAccessFilter​(ReflectionAccessFilter.BLOCK_ALL_ANDROID) (requires Gson 2.9.1 or newer).

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.

parse json android and show it in textview

I am new to android developing, my website returns posts with following format in json:
post= {
'artist':'xxxx',
'title':'xxxx',
'text':'xxxx',
'url':'http://xxxx'
}
I know something about receiving a file from the net and saving it to a SD card, but I want to do it on fly, parse it and show in on some text view, can you please give me some simple code for this?
I tried searching but I can't find a good tutorial for this, so this is the last place I'm coming to solve my problem.
A good framework for parsing XML is Google's GSON.
Basically you could deserialize your XML as follows (import statements left out):
public class Post {
private String artist, title, text, url;
public Post() {} // No args constructor.
}
public class Main {
public static void main(String[] args) {
Gson gson = new Gson();
String jsonString = readFromNetwork(); // Read JSON from network...
Post post = gson.fromJson(jsonString, Post.class);
// Use post instance populated with your JSON data.
}
}
Read more in GSON's user guide.

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()));
}

Approach to serialize cloud endpoint model class to Android file system

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.

Categories

Resources