3

I am using Android's internal storage to hold data for my application and I am trying to find a good way to handle reading data of an older schema. For example, let's say I have serialized and written several instances of class Data to a file

public class Data{
    private String text;
    public Data(text){
        this.text = text;
    }
}

and later I read that stringified data back out and cast to (Data). But then say I change Data to look like this

public class Data{
    private String name;
    public Data(name){
        this.name = name;
    }
}

now that the property is named differently, when I read out data that was saved in the original schema, it will not easily cast to the new type. What are some solutions to version the data so that I can read out old data and cast it to a new type?

2

It's hard to answer this with the information provided. For instance, you say that you're "stringifying" the data, but you aren't saying what method you're using. Is there a library you're using? Is the string JSON, XML, or some other structured data?

I'll assume that you're using some type of structured data (for simplicity, let's say JSON using GSON or similar).

So at write time, you'll create and write a string

public void writeToFile(String filname, Object myObject) {

    String serialized = new Gson().toJson(myObject);

    try(FileOutputStreamoutputStream output =
            openFileOutput(filename, Context.MODE_PRIVATE)
    ) {
        output.write(s.getBytes());
    } catch (Exception e) {
        Log.e("MyApplication",
              "Unable to write " + serialized + " to file " + filename + " with exception",
              e
        );
    }
}

And at read time, you'll want to just read the data back. Well, that's pretty easy

public <T> T read(String filename, Class<T> clazz) {

    String serialized = new Gson().toJson(myObject);

    try (
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                    openFileInput(filename, Context.MODE_PRIVATE)
                )
            )
    ) {
        StringBuilder json = new StringBuilder();

        String line;
        while((line = reader.nextLine()) != null) {
            json.append(line);
        }
        return new Gson().fromJson(json.toString, clazz);
    } catch (Exception e) {
        Log.e(
            "MyApplication",
            "Unable to read " + filename + " to class " + clazz.simpleName() + " with exception",
            e
        );
        return null;
    }
}

But wait, you said that you changed the name of a field. Well, the easiest solution is to just not have changed the name of said field. But this is the real world, there are business requirements, pompous developers who know more than their predecessors, or whatever. You inherited the code. I'm not going to blame you for this.

So, the next easiest solution is to keep older versions of your classes around. For instance, you could wrap your read function with

public <T> T readFromFile(String filename, Transformer<T> optimusPrime) {

    for(Class clazz : optimusPrime.getSupportedClasses()) {
        //TODO optimize this (haha, like that'll get prioritized)
        Object o = read(filename, supportedIterator.next())
        if(o != null) {
            return optimusPrime.transform(o)
        }
    }

    return null;
}

Where your transformer class has a collection of all the possible classes that could have ever been stored in the file (hopefully sorted from most to least likely), and a general method to translate between them. It would probably look something like this

public class OptimusPrime implements Transformer<Autobot> {

    private final Map<Class, TransformMethod> transformations;

    public OptimusPrime() {
        transformations = new TreeMap<>;
        transformations.put(Megatron.class, new MegatronTransform());
        transformations.put(Starscream.class, new StarscreamTransform());
        transformations.put(Shockwave.class, new ShockwaveTransform());
        //Shhhh 
    }

    @Override
    public Collection<Class> getSupportedClasses() {
        return transformations.keySet();
    }

    @Override
    public Autobot transform(Object o) {
        TransformMethod<Autobot> transformation = transformations.get(o.getClass());
        return (transformation == null) ? null : transformation.transform(o);
    }

}

And

public class MegatronTransform implements TransformMethod<Autobot> {
    @Override
    public Megatron transform(Object o){
        return (Megatron) o;
    }
}

So, it's doable, but your code base is going to inflate, and you'll never be able to get rid of all those old classes in your code unless you either give the user some error and recovery method, or you're 100% sure that no users have the legacy data type stored on their device.

What would be a much easier (more expensive) option would be to store the data inside of a versioned API somewhere in the "cloud." That way you can have control over the data and its storage mechanism.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.