The Ultimate Guide to ORM in Android using ActiveAndroid: Part 4

ActiveAndroid Basics – Saving and Querying

All code for this series is located here.

Part 3 of this series gave an in-depth look at how to set up your model classes using ActiveAndroid. This post will focus on how to save your models to your database and how to perform basic queries. We will also implement a basic UI and develop the first iteration of our car building app.

The first version of the app will be very simple. In our main activity we will build a car and save it to the database. The layout will contain a single Button and a TextView. When the button is pressed, we will query the database for our car that we previously saved and display it in the TextView. To see the full object structure I am going to represent the car as a formatted JSON string; this will require the gson library and some helper methods that I will briefly touch upon. To learn more about JSON, check out this link.


Saving Models

To start, let’s look at how we will build our first car. In our MainActivity.java class we will implement a buildCar() method that will piece together our first car and save it to the database.

buildCar() - MainActivity.java
    private static final int SERIAL_NUMBER = 12345678;

    ...


    /**
     * Build the first car
     */
    private void buildCar(){
        // Set up our engine
        Engine e = new Engine();
        e.setCylinders(Engine.CYLINDERS_FOUR);
        e.setVolume(Engine.VOLUME_3_7_L);
        e.setFuelType(Engine.FUEL_TYPE_GAS);
        e.save();

        // Set up our car body
        Body b = new Body();
        b.setBodyStyle(Body.BODY_STYLE_SPORT);
        b.save();

        //Set up our wheels
        Wheel w = new Wheel();
        w.setRims(Wheel.RIMS_SPINNER);
        w.setTires(Wheel.TIRES_WRANGLER);
        w.save();

        // Use our Builder class to build a car
        CarBuilder cBuilder = new CarBuilder();
        Car mCar = cBuilder
                .addSerialNumber(SERIAL_NUMBER)
                .addBody(b)
                .addEngine(e)
                .addWheelsUniform(w)
                .build();

        mCar.save();
    }

Persisting data with ActiveAndroid is extremely easy. After setting the different parameters for our engine class, we call e.save(). This method, which is implemented in the ActiveAndroid Model class, takes care of saving our Engine object to our database; it writes all primitive data members to the database and sets up any foreign key column references. That’s it. That’s all. It really is that simple. Next we set up our Body object and the Wheel object that we are going to use to build our car. Once again, after each respective object is created and the correct member variables are set we call our handy save() method.

Finally we build our car. I implemented a simple CarBuilder class that can assist us in creating our car (check out the source here). The addWheelsUniform(Wheel w) method of the CarBuilder class takes in a single wheel object and sets all four of our cars wheels to that specific wheel. The SERIAL_NUMBER that we pass in will be our unique identifier for our car and will also be how we query our car later. After calling the build() method we are given a Car object that we can then call save() on. IMPORTANT NOTE: You MUST call save() on the car parts BEFORE calling save() on your Car. This is required for ActiveAndroid to properly (in ORM fashion) persist your objects to the database. If you do not save your objects first, when you query your Car all of its non-primitive members will be null.


Querying the Database

Now that we know how to save our first car to the database let’s look at how we go about querying the database for our car. Once again in our MainActivity.java file we will implement a method called loadCar().

loadCar() - MainActivity.java
    private static final int SERIAL_NUMBER = 12345678;

    ...

    /**
     * Load a single car from the database
     */
    private void loadCar(){

        // Check if our Car saved correctly
        boolean exists =
                new Select()
                        .from(Car.class) // Specify the table to search
                        .where("serialNumber = ?", SERIAL_NUMBER) // search criteria
                        .exists(); // Simply ask if it exists

        if(exists) {

            // Load the car from the database
            Car mCar =
                    new Select()
                            .from(Car.class) // Specify the table to search
                            .where("serialNumber = ?", SERIAL_NUMBER) // search criteria
                            .executeSingle(); // return only the first match

            // TODO: Set the TextView to display the formatted json object

        }
        else{
            Log.e(TAG, "loadCar car " + SERIAL_NUMBER + " does not exist!");
        }
    }

    ...

The query in lines 10-15 from the snippet above is checking to see if an object exists in the database. Let’s break this down one piece at a time:

  • By calling new Select() we are creating a new ActiveAndroid database query.
  • The .from(Car.class) method tells ActiveAndroid we want to query the table containing our Car objects.
  • To search for a specific parameter (in our case the Car’s serialNumber) we call .where("serialNumber = ?", SERIAL_NUMBER).
  • Since we only want to know if the Car even exists at the point, we call the .exists() method that returns true if the object exists and false otherwise.

As seen above, queries with ActiveAndroid are easy to build and read exactly as they function. Moving on, if a Car with the given serial number does exist then we want to load the actual object from the database and display it. In the event that it doesn’t exist, for now we’ll just log an error message. The query in lines 19-24 from above is basically identical to the first query except for the final action. To retrieve the Car object itself, we call .executeSingle() which returns a Car object with the given serial number. The Select class also has a .execute() method that will return a list of all Car objects that match the query parameters.


Displaying our Car

As I mentioned previously, I am going to use a formatted JSON string to display our object. This will require some simple helper methods to be added to our existing model classes and main activity. So first thing first – we need to get gson up and running in our app. We’ll need to update our apps build.gradle file to include the gson library dependency:

build.gradle

...

dependencies {
    
    ...   
    
    compile 'com.google.code.gson:gson:2.4'
}

Now we need to actually represent our car as a JSON object. If you noticed in the last tutorial, our CarPart class that our Engine, Wheel, and Body classes are derived from includes a public JsonObject getJsonObject() method. Our other classes will override this method to properly format themselves. Since the Classes that make up our Car contain only primitives, it is easy to build JsonObjects for each by using the overloaded addProperty(String name, <type> value) method that is part of the JsonObject class. The getJsonObject() implementations for each of our CarParts are below:

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

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

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

    public static final String FUEL_TYPE_GAS = "gas";
    public static final int CYLINDERS_FOUR = 4;
    public static final double VOLUME_3_7_L = 3.7;

    public static final String TABLE_NAME = "Engines";

    private static final String COLUMN_VOLUME = "volume";
    private static final String COLUMN_CYLINDERS = "cylinders";
    private static final String COLUMN_FUELTYPE = "fuelType";

    @Column(name = COLUMN_VOLUME)
    private double volume;

    @Column(name = COLUMN_CYLINDERS)
    private int cylinders;

    @Column(name = COLUMN_FUELTYPE)
    private String fuelType;

    ...

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

        obj.addProperty(COLUMN_VOLUME, volume);
        obj.addProperty(COLUMN_CYLINDERS, cylinders);
        obj.addProperty(COLUMN_FUELTYPE, fuelType);

        return obj;
    }
}
Body.java
package com.rscottcarson.activeandroid_tutorial.models;

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

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

    public static final int BODY_STYLE_SPORT = 1;

    public static final String TABLE_NAME = "Bodies";

    private static final String COLUMN_BODYSTYLE = "bodyStyle";

    @Column(name = COLUMN_BODYSTYLE)
    private int bodyStyle;

    ...

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

        obj.addProperty(COLUMN_BODYSTYLE, bodyStyle);

        return obj;
    }
}
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;
    }
}

Building the JsonObject for our Car class is still easy, but takes a little more JSON knowledge.

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


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

import java.util.List;

@Table(name = Car.TABLE_NAME)
public class Car extends Model {

    // Declare table name as public
    public static final String TABLE_NAME = "Cars";

    // Declare all column names private
    private static final String COLUMN_ENGINE = "engine";
    private static final String COLUMN_BODY = "body";
    private static final String COLUMN_WHEEL = "wheels";
    private static final String COLUMN_SERIAL_NUMBER = "serialNumber";

    @Column(name = COLUMN_ENGINE)
    private Engine engine;

    @Column(name = COLUMN_BODY)
    private Body body;

    @Column(name = COLUMN_WHEEL)
    private List<Wheel> wheels;

    @Column(name = COLUMN_SERIAL_NUMBER,
            unique = true,
            onUniqueConflict = Column.ConflictAction.REPLACE,
            index = true)
    private int serialNumber;

    ...

    /**
     * Build a JsonArray for display purposes
     * @return
     */
    private JsonArray getWheelsList(){
        JsonArray arr = new JsonArray();

        for(Wheel w : wheels){
            arr.add(w.getJsonObject());
        }

        return arr;
    }

    /**
     * Get the JSON representation of the car - for display purposes
     * @return
     */
    public JsonObject getJsonObject(){

        JsonObject car = new JsonObject();
        JsonObject obj = new JsonObject();

        obj.addProperty(COLUMN_SERIAL_NUMBER, serialNumber);
        obj.add(COLUMN_ENGINE, engine.getJsonObject());
        obj.add(COLUMN_BODY, body.getJsonObject());

        // TODO: Implement TypeSerializer for List<>
        //obj.add(COLUMN_WHEEL, getWheelsList());

        car.add("Car", obj);

        return car;
    }

}

For readability, we create two JsonObjects: one to encapsulate the entire Car and one to build up the object hierarchy. For the serialNumber we use the basic addProperty() method. However for our CarPart member variables we call the add(String name, JsonElement element) method. For now we are only adding JsonObjects, but we will also be adding JsonArrays in the future. Note that we are not adding the list of wheels to the JsonObject just yet. This is because ActiveAndroid can’t handle Java’s List type; we will implement a custom TypeSerializer to fix this in the next part of the series. So for now we will have a wheel-less car; not great for driving but it’s a start!

Finally we can set up our UI, wire everything together, and try out our app. Below is the simple main_activity.xml:

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"/>

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

Now let’s make some additions to our MainActivity.java class:

MainActivity.java
package com.rscottcarson.activeandroid_tutorial.activities;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.activeandroid.query.Select;
import com.rscottcarson.activeandroid_tutorial.R;
import com.rscottcarson.activeandroid_tutorial.models.Body;
import com.rscottcarson.activeandroid_tutorial.models.Car;
import com.rscottcarson.activeandroid_tutorial.models.Engine;
import com.rscottcarson.activeandroid_tutorial.models.Wheel;
import com.rscottcarson.activeandroid_tutorial.models.model_builders.*;

public class MainActivity extends AppCompatActivity {

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

    private static final int SERIAL_NUMBER = 12345678;

    private Button mButton;
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButton = (Button) findViewById(R.id.btn);
        mTextView = (TextView) findViewById(R.id.tv1);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                loadCar();
            }
        });

        buildCar();
    }

    /**
     * Build the first car
     */
    private void buildCar(){
        // Set up our engine
        Engine e = new Engine();
        e.setCylinders(Engine.CYLINDERS_FOUR);
        e.setVolume(Engine.VOLUME_3_7_L);
        e.setFuelType(Engine.FUEL_TYPE_GAS);
        e.save();

        // Set up our car body
        Body b = new Body();
        b.setBodyStyle(Body.BODY_STYLE_SPORT);
        b.save();

        //Set up our wheels
        Wheel w = new Wheel();
        w.setRims(Wheel.RIMS_SPINNER);
        w.setTires(Wheel.TIRES_WRANGLER);
        w.save();

        // Use our Builder class to build a car
        CarBuilder cBuilder = new CarBuilder();
        Car mCar = cBuilder
                .addSerialNumber(SERIAL_NUMBER)
                .addBody(b)
                .addEngine(e)
                .addWheelsUniform(w)
                .build();

        mCar.save();

    }

    /**
     * Load a single car from the database
     */
    private void loadCar(){

        // Check if our Car saved correctly
        boolean exists =
                new Select()
                        .from(Car.class) // Specify the table to search
                        .where("serialNumber = ?", SERIAL_NUMBER) // search criteria
                        .exists(); // Simply ask if it exists

        if(exists) {

            // Load the car from the database
            Car mCar =
                    new Select()
                            .from(Car.class) // Specify the table to search
                            .where("serialNumber = ?", SERIAL_NUMBER) // search criteria
                            .executeSingle(); // return only the first match

            // Set the TextView to display the formatted json object
            mTextView.setText(formatString(mCar.getJsonObject().toString()));
        }
        else{
            Log.e(TAG, "loadCar car " + SERIAL_NUMBER + " does not exist!");
        }
    }

    /**
     * Take a Json string and format it to display nicely
     * @param text
     * @return
     */
    public static String formatString(String text){

        StringBuilder json = new StringBuilder();
        String indentString = "";

        for (int i = 0; i < text.length(); i++) {
            char letter = text.charAt(i);
            switch (letter) {
                case '{':
                case '[':
                    json.append("\n" + indentString + letter + "\n");
                    indentString = indentString + "\t";
                    json.append(indentString);
                    break;
                case '}':
                case ']':
                    indentString = indentString.replaceFirst("\t", "");
                    json.append("\n" + indentString + letter);
                    break;
                case ',':
                    json.append(letter + "\n" + indentString);
                    break;

                default:
                    json.append(letter);
                    break;
            }
        }

        return json.toString();
    }
}

The formatString(String text) helper method takes our JsonObject as a string and formats it to be a pretty, properly indented String that we can stick directly in our TextView. This will allow us to view the object hierarchy inherent in our Car model class. In our onCreate() we find our views, set up our button’s OnClickListener, and build our car. Nothing fancy here, just your standard Android UI boilerplate. In our loadCar() method we set our TextView to display our Car as a formatted JSON String.

Below you can check out two screenshots I took while testing the app; one is before the button is pressed and the other is after the button is pressed.

So that’s it! We have successfully built a car, saved it to our database, queried it, and displayed it again after retrieving it from the database. For simple applications this may be all you need, but there are a lot more features of ActiveAndroid that are worth exploring.


What is next?

In the next post we will look at implementing a custom TypeSerializer for Java’s List class.

Part 5: TypeSerializers


Helpful Links

Leave a Reply

Your email address will not be published. Required fields are marked *