ActiveAndroid TypeSerializers

In the last post we learned the basics of saving and querying with ActiveAndroid. We built a simple UI for our app and glimpsed our first tangible example of ActiveAndroid at work. This post will focus on an important part of developing with ActiveAndroid: TypeSerializers.

ActiveAndroid knows how to store (serialize) primitives and your custom Model classes. It does not, and can not, know how to serialize/deserialize every Object used in Java. Some examples of commonly used types that ActiveAndroid does not know how to store are Lists, Maps, and Dates. Many classes will want to take advantage of these classes and data structures, so ActiveAndroid defined a TypeSerializer abstract class that can be extended to handle the serialization and deserialization of these data types.

TypeSerializers are not always pretty, and a bit of thought has to go into how to handle a generic type (such a List<?>). For our car building app, the Car class has a List<Wheel> data member that ActiveAndroid can not handle. In Part 4 of this series we simply ignored trying to display our wheels because we knew they were not going to be saved correctly and therefor not recalled correctly. The focus now will be to write a custom TypeSerializer that can not just handle our List<Wheel> but could be easily modified to handle any List<?> in the future.

Setting Up

For our List TypeSerializer we are going to utilize more features of the gson library to make our lives easier. This will require a few modifications to our CarBuilderApplication class:

CarBuilderApplication.java
package com.rscottcarson.activeandroid_tutorial;

import com.activeandroid.app.Application;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.lang.reflect.Modifier;

public class CarBuilderApplication extends Application {

    private static CarBuilderApplication sInstance;

    private Gson gson;

    @Override
    public void onCreate(){
        super.onCreate();

        // build new gson instance
        gson = new GsonBuilder()
                .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC)
                .create();

        sInstance = this;
    }

    /**
     * Get global gson instance
     * @return

    public Gson getGson(){
        return gson;
    }

    /**
     * Restrict access to the app through a static instance
     * @return

    public static CarBuilderApplication getApp(){
        return sInstance;
    }
}

I want to create a single Gson object that is used throughout the app. To do this I basically implement the Singleton design pattern on our CarBuilderApplication class. In the onCreate() we first set up our global Gson instance using the GsonBuilder class. The only interesting part here is that we explicitly tell gson to ignore any variables with final, static, or transient modifiers. The modifiers final and static are ignored because one is a constant and the other is shared between instances of a class, respectively. Although not often used, we ignore the transient modifier because variables declared with this modifier are not serialized, thus would never be saved to our database.

Once our Gson object is built we set our static application instance to be the instance we just created. This enforces the Singleton design pattern because we know our app will only create a single application instance at any given time and we also know that our application object will be available anytime the app is running. No we can restrict access to our application object through the static getApp() method. This allows us to safely create global instances of resources to be used throughout our app. It is valuable to note that the Singleton design pattern is not always the best solution, but for our purposes it works just fine.

Now we need to modify one line of our AndroidManifest.xml:

AndroidManifest.xml
...

    <application

        ...
        android:name=".CarBuilderApplication">

    ...

    </application>

</manifest>

By changing the android:name value under the <application> tag we point directly to our CarBuilderApplication class instead of the ActiveAndroid application class (which our CarBuilderApplication class extends).

Implementing the TypeSerializer

Now we will take a look at how to implement our TypeSerializer class. When serializing an object to store in our database we must think about how that object will be represented in our database. Remember that SQLite supports four data types: INTEGER, TEXT, REAL, NONE. This means we need to come up with a way to represent our the Java List type as one of those four types. Not only that, but we need to create a mapping that is easy to deserialize back to the original object as well.

For many custom TypeSerializers it is easiest to convert the object to a String (or TEXT) representation. Not only that, but we know we can accurately represent objects, lists, and primitives using JSON! Our TypeSerializer will take our List object and convert it to a JSONArray that can then be saved as a string. The advantage to this is that the son library has a handy function, fromJson(String s, Class<?> class), that takes a properly formatted JSON string and a POJO class then (if successful) returns an initialized Object of the given class type.

Let’s take a look at our ListTypeSerializer extension of the TypeSerializer class:

ListTypeSerializer.java
package com.rscottcarson.activeandroid_tutorial.type_serializers;

import android.util.Log;

import com.activeandroid.serializer.TypeSerializer;
import com.rscottcarson.activeandroid_tutorial.CarBuilderApplication;
import com.rscottcarson.activeandroid_tutorial.models.Wheel;
import com.rscottcarson.activeandroid_tutorial.models.gson_types.GetWheelList;

import java.util.List;

public class ListTypeSerializer extends TypeSerializer {

    private static final String TAG = ListTypeSerializer.class.getSimpleName();


    @Override
    public Class<?> getDeserializedType() {
        return List.class;
    }


    @Override
    public Class<?> getSerializedType() {
        return String.class;
    }


    @Override
    public String serialize(Object data) {

        Log.i(TAG, "in serialize");

        StringBuilder sBuilder = new StringBuilder();

        // If we succesfully convert it to a String, print out the resulting string,
        // otherwise print an error
        if(getListAsJsonString(data, sBuilder)){
            Log.i(TAG, "serialize: " + sBuilder.toString());
        }
        else{
            Log.e(TAG, "Error ");
        }

        return sBuilder.toString();
    }


    *
     * Helper function to build the Serialized Json String of our wheel list. It is written
     * in such a way that we could potentially add more List<T> types other than List<Wheel>
     * @param o data Object
     * @param sBuilder Reference to stringbuilder
     * @return False if the object is not a supported List<T> type

    private boolean getListAsJsonString(Object o, StringBuilder sBuilder){

        if(o instanceof List){
            Log.i(TAG, "getListType is a List");

            List<?> list = (List<?>) o;

            if(!list.isEmpty()){
                Object type = list.get(0);

                if(type instanceof Wheel){
                    List<Wheel> finalList = (List<Wheel>) list;
                    sBuilder.append("{\"")
                            .append("wheelList")
                            .append("\":")
                            .append(finalList.toString())
                            .append("}");
                    return true;
                }
            }
        }

        return false;
    }


    @Override
    public List<?> deserialize(Object data) {

        Log.i(TAG, "in deserialize");

        String s = (String) data;

        // Get our wheelList using our global gson instance
        if(s.contains("wheelList")){
            GetWheelList gwl = CarBuilderApplication
                    .getApp()
                    .getGson()
                    .fromJson(s, GetWheelList.class);

            return gwl.wheelList;
        }
        else {
            return null;
        }
    }

}

Now I’ll break down our ListTypeSerializer piece-by-piece.

First and foremost we must extend the ActiveAndroid TypeSerializer abstract class. Doing so forces us to implement four methods. The methods getDeserializedType() and getSerializedType() return the type of the object we need to save and the type we will use represent it when it is saved, respectively. For us the deserialized type is Java’s List class and the serialized type is a String.

A more interesting method that we have to implement is serialize(Object data). This method takes in an object (presumably of type List, but we check just in case) and returns the object as a properly serialized String that can be saved to the database. Our getListAsJsonString(Object o, StringBuilder sBuilder) helper method does most of the serialization leg work. This method ensures that our Object is indeed a List, then checks that the List is of type Wheel. The code here is a little verbose, but I have not found a slicker way of doing such a check in Java. Once we know we have a List we build our String up to be a properly formatted JSON object. One reason this works so well is that the List toString() implementation already partially formats the string as a JSON array. All we have to do is tack on a property name (wheelList) and then override the Wheel classes’ toString() (which we’ll look at soon). If we successfully build a String then we return true and print out our JSON string, otherwise we return false and log an error. Finally, we return our List formatted as a JSON string. The String should look something like this (depending on the Wheels properties):

{"wheelList":[{"rims":"spinner","tires":"Goodyear wrangler"}, {"rims":"spinner","tires":"Goodyear wrangler"}, {"rims":"spinner","tires":"Goodyear wrangler"}, {"rims":"spinner","tires":"Goodyear wrangler"}]}

Which, when formatted properly, looks just like a nice JSON object list:

  {  
   "wheelList":[
      {        
         "rims":"spinner",
         "tires":"Goodyear wrangler"
      },
      {  
         "rims":"spinner",
         "tires":"Goodyear wrangler"
      },
      {  
         "rims":"spinner",
         "tires":"Goodyear wrangler"
      },
      {  
         "rims":"spinner",
         "tires":"Goodyear wrangler"
      }
    ]
}

Next we have to implement the opposite; deserialize(Object data) takes an object (this time we’re assuming it’s a String) and returns it as a List. The meat of this method is performed in the call to gson’s .fromJson(String, Class<?>). The POJO that gson will populate is a very simple class:

GetWheelList.java
package com.rscottcarson.activeandroid_tutorial.models.gson_types;

import com.rscottcarson.activeandroid_tutorial.models.Wheel;

import java.util.List;

public class GetWheelList {

    public List<Wheel> wheelList;
}

If successful, we can return gwl.wheelList which contains the list of wheel objects we previously saved to the database.

Next I want to look at how we override the Wheel class’ toString() method and the CarPart class’ getJsonObject() method to give us a nice looking JSON object.

Wheel.java
package com.rscottcarson.activeandroid_tutorial.models;

import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
import com.google.gson.JsonObject;

@Table(name = Wheel.TABLE_NAME)
public class Wheel extends CarPart {

    public static final String RIMS_SPINNER = "spinner";
    public static final String TIRES_WRANGLER = "Goodyear wrangler";

    public static final String TABLE_NAME = "Wheels";

    private static final String COLUMN_RIMS = "rims";
    private static final String COLUMN_TIRES = "tires";

    @Column(name = COLUMN_RIMS)
    private String rims;

    @Column(name = COLUMN_TIRES)
    private String tires;

    ...

    @Override
    public JsonObject getJsonObject(){
        JsonObject obj = new JsonObject();

        obj.addProperty(COLUMN_RIMS, rims);
        obj.addProperty(COLUMN_TIRES, tires);

        return obj;
    }

    @Override
    public String toString(){
        String s = getJsonObject().toString();

        return s;
    }
}

Overall, pretty easy to understand. Simply build our JSON object and then ‘stringify’ it.

Putting It All Together

Last but not least let’s change one item in our UI. Now that we know we’ll be printing out list of all the wheels, I want to wrap our TextView in a ScrollView so we can actually see it all!

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".activities.MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_load_car_text"/>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    </ScrollView>
</LinearLayout>

Now when we run our app we should see all of our wheels that we saved to our database.

That is the basics of TypeSerializers with ActiveAndroid! They are an extremely handy and necessary tool when dealing with complex data types and object hierarchies.

What is next?

In the next post we will look at using the content provider to query our database and construct a list of car objects.

Part 6: Using the Content Provider (coming soon)

Helpful Links