AutoValue - get rid of boilerplate code in your Android app

In this article I want to introduce you to AutoValue library developed by Google. Here, @ RST IT, we use it for implementing models in Android applications. It greatly speeds up the code writing process by eliminating almost all boilerplate part of it. I will compare your models (assuming that they are written as POJOs) and my models written as AutoValue classes.

Models in your project

Let me show you your typical (yet very, very simple) user model:

public class YourUser {  
    public String firstName;
    public String lastName;
    public String zipCode;
}

Let’s look into it. YourUser class has 3 fields storing data about user in your app. But there is one, huge problem with it - those fields are public and should be private with getters and setters. You can read why in this StackOverflow answer. Then you refactor it to use private fields with getters and setters, because now you are a wise man.

public class YourUser {

    private String firstName;
    private String lastName;
    private String zipCode;

    public @NonNull String getFirstName() {
        return firstName;
    }

    public void setFirstName(@NonNull String firstName) {
        this.firstName = checkNotNull(firstName);
    }

    public @NonNull String getLastName() {
        return lastName;
    }

    public void setLastName(@NonNull String lastName) {
        this.lastName = checkNotNull(lastName);
    }

    public @NonNull String getZipCode() {
        return zipCode;
    }

    public void setZipCode(@NonNull String zipCode) {
        this.zipCode = checkNotNull(zipCode);
    }
}

The common practice is to check if user is also author of some other in-app entity (like e.g. post or comment) to show him more actions he is able to perform with this entity. So to be able to check if two users are in fact one and the same user you have to implement Object’s equals method:

@Override
public boolean equals(@Nullable Object obj) {  
    if (obj == this) {
        return true;
    }
    if (obj instanceof YourUser) {
        YourUser other = (YourUser) obj;
        return other.firstName.equals(firstName)
                && other.lastName.equals(lastName)
                && other.zipCode.equals(zipCode);
    }
    return false;
}

The good practice is to never implement equals without hashCode and the other way around. You always follow good principles so you implement hashCode you are missing in your code:

@Override
public int hashCode() {  
    int h = 1;
    h *= 1000003;
    h ^= firstName.hashCode();
    h *= 1000003;
    h ^= lastName.hashCode();
    h *= 1000003;
    h ^= zipCode.hashCode();
    return h;
}

The next Object’s method worth implementing is toString. This method should return String representation of your object (that can be helpful for debugging and e.g. show current fields values). So you write the following:

@Override
public String toString() {  
    return "YourUser{"
            + "firstName=" + firstName + ", "
            + "lastName=" + lastName + ", "
            + "zipCode=" + zipCode
            + "}";
}

Your model is almost done. We just forgot you are an Android developer and there are places in your app where you want to pass your user model with Intent.

Implementing parcelable

Intent can store primitive values or objects of classes which implement Parcelable (or Serializable, but Parcelable is far more memory efficient), an Android interface that, well, parcels data. So then you make YourUser implement Parcelable and write this long implementation…

public class YourUser implements Parcelable  
(...)
protected YourUser(@NonNull Parcel in) {  
    firstName = in.readString();
    lastName = in.readString();
    zipCode = in.readString();
}

@Override
public int describeContents() {  
    return 0;
}

@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {  
    dest.writeString(firstName);
    dest.writeString(lastName);
    dest.writeString(zipCode);
}

@SuppressWarnings("unused")
public static final Parcelable.Creator<YourUser> CREATOR = new Parcelable.Creator<YourUser>() {

    @Override
    public @NonNull YourUser createFromParcel(@NonNull Parcel in) {
        return new YourUser(checkNotNull(in));
    }

    @Override
    public @NonNull YourUser[] newArray(int size) {
        return new YourUser[size];
    }

};

Networking - JSON de/serialization

Fortunately, you don’t have to write any code to be able to serialize and deserialize your object to/from JSON using Gson library. You can use this time to relax your fingers that were hard tapping the keyboard while writing all this code.

Summary

You are finally done! Now YourUser class has 3 lines of code that are really core and over 90 lines of boilerplate code that will be very similar in every other model class. The only difference would be field types and names. So if only there were a library that generates all this boilerplate code, leaving us just the true core of the model - fields...

AutoValue to the rescue!

I do not like to write boilerplate code (none of us probably does). And because of that I chose AutoValue library to implement models in my app. AutoValue uses code generation to implement the boilerplate code part of your model. It also allows you to pin your plugins into it, making it a really powerful programming tool. To add AutoValue to your project you need android-apt gradle plugin (see section “Including and using the plugin in your build script”). When you’re done paste those lines in the build.gradle script of your app module (at the moment of writing this article version 1.3 is the most recent supported by extensions I introduce you to later in this article):

provided ‘com.google.auto.value:auto-value:1.3’  
apt ‘com.google.auto.value:auto-value:1.3’  

So here is what my user model looks like:

@AutoValue
public abstract class MyUser {

    public abstract @NonNull String firstName();
    public abstract @NonNull String lastName();
    public abstract @NonNull String zipCode();

    public @NonNull Builder toBuilder() {
        return new AutoValue_MyUser.Builder(this);
    }

    public static @NonNull Builder builder() {
        return new AutoValue_MyUser.Builder();
    }

    @AutoValue.Builder
    public abstract static class Builder {

        public abstract @NonNull Builder setFirstName(@NonNull String firstName);
        public abstract @NonNull Builder setLastName(@NonNull String lastName);
        public abstract @NonNull Builder setZipCode(@NonNull String zipCode);
        public abstract @NonNull MyUser build();

    }

}

You may wonder what is going on here...so let me share an explanation. Firstly I define abstract class MyUser with abstract methods. Each method represents one field in YourUser model class. Annotating such a class with @AutoValue annotation will tell AutoValue to generate AutoValue_MyUser class. This generated class has fields with signatures matching declared abstract methods and those methods overridden to serve this fields. It also has equals, hashCode and toString implemented. The main difference between YourUser and MyUser is that MyUser is immutable. That means its values are set when it is created and after that they cannot be changed. To be able to create MyUser with set values AutoValue uses builder pattern.

I have just created a nested static class annotated with @AutoValue.Builder annotation. There I declare abstract methods for setting fields I want to be able to set. And at the end another abstract method called build returning MyUser type. Then in MyUser class I create two methods:

  • static method returning Builder - this one returns fresh instance of Builder without any fields set
  • instance method returning Builder - this one returns Builder instance with fields initially set to values of this object.

So at this point MyUser has functionalities of YourUser with getters and setters, equals, hashCode and toString implemented. What we miss is Parcelable and JSON conversion. Fortunately, there are AutoValue plugins allowing us to add those missing functionalities to our class.

AutoValue plugins

Parcel extension

This extension will make MyUser implement Parcelable interface. To be able to use it paste the below code in the build.gradle script of your app module (at the moment of writing this article 0.2.5 is the most recent version):

apt 'com.ryanharter.auto.value:auto-value-parcel:0.2.5'  

and if you want to write custom TypeAdapters add also:

compile 'com.ryanharter.auto.value:auto-value-parcel-adapter:0.2.5'  

I will guide you carefully through the steps I had to take to achieve it. Please take your time and read it without leaving any of the steps, because all of them are required to properly make this class Parcelable. So here we go:

  1. Indicate that MyUser is implementing Parcelable by writing
    public abstract class MyUser implements Parcelable
  2. ???
  3. PROFIT!

And that is really it! If you are using field of type that doesn’t implement Parcelable you can write your own type adapter and annotate its abstract method with @ParcelAdapter.

Gson extension

Models are often fetched from web API as JSONs. To convert it to Java object we can use one of many libraries available out there. One of the most popular ones is Gson. It is also convenient to use, because there is a Gson converter for Retrofit. If you want your AutoValue classes to work with Gson add those lines in the build.gradle script of your app module (at the moment of writing this article 0.4.6 is the most recent version):

apt 'com.ryanharter.auto.value:auto-value-gson:0.4.6'  
provided 'com.ryanharter.auto.value:auto-value-gson:0.4.6'  

So how do I make MyUser cooperate with Gson? Very easy! I just have to implement static method returning TypeAdapter<MyUser>. And since AutoValue Gson extension generates such an adapter this method looks like this:

public static @NonNull TypeAdapter<MyUser> typeAdapter(@NonNull Gson gson) {  
    return new AutoValue_MyUser.GsonTypeAdapter(checkNotNull(gson));
}

Now I implement AutoValueGsonTypeAdapterFactory class annotated with @GsonTypeAdapterFactory. Thanks to this annotation AutoValue Gson extension will generate factory class that will handle every AutoValue annotated class with static method returning TypeAdapter<T>. Here is the class code I had to write:

@GsonTypeAdapterFactory
public abstract class AutoValueGsonTypeAdapterFactory implements TypeAdapterFactory {

    public static @NonNull TypeAdapterFactory create() {
        return new AutoValueGson_AutoValueGsonTypeAdapterFactory();
    }

}

And the last step - I have to register this factory when creating Gson instance:

Gson gson = new GsonBuilder()  
        .registerTypeAdapterFactory(AutoValueGsonTypeAdapterFactory.create())
        .create();

Looking at Parcel extension you might say that adding Gson extension is really complicated, but be honest and say that in fact it is really simple.

Conclusion

Using AutoValue classes as models in your application is really handy. It frees you from writing a lot of boilerplate code. This way any change to model class is really simple and requires minimal refactoring or even better - no refactoring at all. Powerful plugins give you anything you will ever need in your Android application. And if by any chance you need something more just search it through AutoValue documentation. There is a high chance that it is already implemented, but not touched upon in this article.