Hi folks,

Today, I would like to express the way how we (in Loodos) add new features by using some of the cool stuff that helps to prevent writing boilerplate or same code blocks on our own every time.

The reason I have decided to write about this is we have discovered these helpers a little late and I’d like to stop this to happen you if you have same development flow as we have.

I will try to use distinct example (adding new feature) and try to give them like real life issues, let’s begin.

TL;DR we started using jsonschema2pojo & AVB code generator to implement new features that backend services provided.

We as developers are not interested in whole analyzing, planning and empty chit-chatting meetings, so I’m going to start from the moment we received a design and service document.

Since it is not recommended to use one of private service response, I am going to use JSON generator to show what assumingly we got from our backend provider:

[
  {
    "_id": "5a3d202a2c543f89e3a8fd39",
    "index": 0,
    "guid": "4867b1ad-504a-443b-96a6-04e4d4fd5b1b",
    "isActive": true,
    "balance": "$2,869.73",
    "picture": "http://placehold.it/32x32",
    "age": 33,
    "eyeColor": "blue",
    "name": "Wendi Gilmore",
    "gender": "female",
    "company": "ENERVATE",
    "email": "wendigilmore@enervate.com",
    "phone": "+1 (919) 477-2153",
    "address": "475 Verona Street, Malott, Vermont, 6666",
    "about": "Enim incididunt fugiat duis mollit ullamco aliqua consequat mollit in cupidatat consequat. Ea magna minim minim laborum do anim ex ea labore cupidatat sunt aliquip officia consectetur. Esse ut amet anim culpa est nulla fugiat occaecat anim pariatur veniam mollit. Pariatur velit in aliqua elit magna proident magna eu excepteur et. Do est consequat ex dolore esse non nostrud officia cupidatat dolore qui.\r\n",
    "registered": "2015-04-29T04:37:10 -04:00",
    "latitude": 45.516706,
    "longitude": -22.986577,
    "tags": [
      "tempor",
      "Lorem",
      "et",
      "aliqua",
      "eu",
      "est",
      "exercitation"
    ],
    "friends": [
      {
        "id": 0,
        "name": "Lorie Kline"
      },
      {
        "id": 1,
        "name": "Weiss Long"
      },
      {
        "id": 2,
        "name": "Serena Carson"
      }
    ],
    "greeting": "Hello, Wendi Gilmore! You have 4 unread messages.",
    "favoriteFruit": "apple"
  },
  ...
]

What we were doing back in the dark ages was implementing every part of this json in a response class (and not as Parcelable but Serializable!)

“Look to my coming, at first light, on the fifth day. At dawn, look to the East.” — Gandalf the White

So at dawn of the fifth work day, we have discovered jsonschema2pojo (shame on us because it is not a new thing, its first release date is 2011 !!!) and start to try it, indeterminedly.

What jsonschema2pojo doing is pretty simple, within some configuration (target lang, source type, annotation style, etc.) it transforms the json to plain old java objects. Let’s see the trick:

-----------------------------------com.example.packagename.Friend.java-----------------------------------

package com.example.packagename;

import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable.Creator;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Friend implements Parcelable
{

    @SerializedName("id")
    @Expose
    public Integer id;
    @SerializedName("name")
    @Expose
    public String name;
    public final static Parcelable.Creator<Friend> CREATOR = new Creator<Friend>() {


        @SuppressWarnings({
                "unchecked"
        })
        public Friend createFromParcel(Parcel in) {
            return new Friend(in);
        }

        public Friend[] newArray(int size) {
            return (new Friend[size]);
        }

    }
            ;

    protected Friend(Parcel in) {
        this.id = ((Integer) in.readValue((Integer.class.getClassLoader())));
        this.name = ((String) in.readValue((String.class.getClassLoader())));
    }

    public Friend() {
    }

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeValue(id);
        dest.writeValue(name);
    }

    public int describeContents() {
        return 0;
    }

}
-----------------------------------com.example.packagename.Response.java-----------------------------------

package com.example.packagename;

import java.util.List;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable.Creator;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Response implements Parcelable
{

    @SerializedName("_id")
    @Expose
    public String id;
    @SerializedName("index")
    @Expose
    public Integer index;
    @SerializedName("guid")
    @Expose
    public String guid;
    @SerializedName("isActive")
    @Expose
    public Boolean isActive;
    @SerializedName("balance")
    @Expose
    public String balance;
    @SerializedName("picture")
    @Expose
    public String picture;
    @SerializedName("age")
    @Expose
    public Integer age;
    @SerializedName("eyeColor")
    @Expose
    public String eyeColor;
    @SerializedName("name")
    @Expose
    public String name;
    @SerializedName("gender")
    @Expose
    public String gender;
    @SerializedName("company")
    @Expose
    public String company;
    @SerializedName("email")
    @Expose
    public String email;
    @SerializedName("phone")
    @Expose
    public String phone;
    @SerializedName("address")
    @Expose
    public String address;
    @SerializedName("about")
    @Expose
    public String about;
    @SerializedName("registered")
    @Expose
    public String registered;
    @SerializedName("latitude")
    @Expose
    public Double latitude;
    @SerializedName("longitude")
    @Expose
    public Double longitude;
    @SerializedName("tags")
    @Expose
    public List<String> tags = null;
    @SerializedName("friends")
    @Expose
    public List<Friend> friends = null;
    @SerializedName("greeting")
    @Expose
    public String greeting;
    @SerializedName("favoriteFruit")
    @Expose
    public String favoriteFruit;
    public final static Parcelable.Creator<Response> CREATOR = new Creator<Response>() {


        @SuppressWarnings({
                "unchecked"
        })
        public Response createFromParcel(Parcel in) {
            return new Response(in);
        }

        public Response[] newArray(int size) {
            return (new Response[size]);
        }

    }
            ;

    protected Response(Parcel in) {
        this.id = ((String) in.readValue((String.class.getClassLoader())));
        this.index = ((Integer) in.readValue((Integer.class.getClassLoader())));
        this.guid = ((String) in.readValue((String.class.getClassLoader())));
        this.isActive = ((Boolean) in.readValue((Boolean.class.getClassLoader())));
        this.balance = ((String) in.readValue((String.class.getClassLoader())));
        this.picture = ((String) in.readValue((String.class.getClassLoader())));
        this.age = ((Integer) in.readValue((Integer.class.getClassLoader())));
        this.eyeColor = ((String) in.readValue((String.class.getClassLoader())));
        this.name = ((String) in.readValue((String.class.getClassLoader())));
        this.gender = ((String) in.readValue((String.class.getClassLoader())));
        this.company = ((String) in.readValue((String.class.getClassLoader())));
        this.email = ((String) in.readValue((String.class.getClassLoader())));
        this.phone = ((String) in.readValue((String.class.getClassLoader())));
        this.address = ((String) in.readValue((String.class.getClassLoader())));
        this.about = ((String) in.readValue((String.class.getClassLoader())));
        this.registered = ((String) in.readValue((String.class.getClassLoader())));
        this.latitude = ((Double) in.readValue((Double.class.getClassLoader())));
        this.longitude = ((Double) in.readValue((Double.class.getClassLoader())));
        in.readList(this.tags, (java.lang.String.class.getClassLoader()));
        in.readList(this.friends, (com.example.packagename.Friend.class.getClassLoader()));
        this.greeting = ((String) in.readValue((String.class.getClassLoader())));
        this.favoriteFruit = ((String) in.readValue((String.class.getClassLoader())));
    }

    public Response() {
    }

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeValue(id);
        dest.writeValue(index);
        dest.writeValue(guid);
        dest.writeValue(isActive);
        dest.writeValue(balance);
        dest.writeValue(picture);
        dest.writeValue(age);
        dest.writeValue(eyeColor);
        dest.writeValue(name);
        dest.writeValue(gender);
        dest.writeValue(company);
        dest.writeValue(email);
        dest.writeValue(phone);
        dest.writeValue(address);
        dest.writeValue(about);
        dest.writeValue(registered);
        dest.writeValue(latitude);
        dest.writeValue(longitude);
        dest.writeList(tags);
        dest.writeList(friends);
        dest.writeValue(greeting);
        dest.writeValue(favoriteFruit);
    }

    public int describeContents() {
        return 0;
    }

}

For a brief moment, I want you to think about how time saving this tool is


Thanks,

So, we have our data classes generated, smoothly. I’d like to warn about several issues before moving to the second phase:

  • Check out the generated code since it can contain some base object you have already have, the tool doesn’t know that and creates a new one.
  • While generating, it uses object classes of known types like int as Integer or boolean as Boolean, you may want to change these types to prevent NPEs.

--

Let us continue with the designing screens part. What we have now are the data classes and we have the concept design of them. We’re going to create the layout/XML that’ll be bind.

To be honest, I couldn’t manage to create a layout xml for all of the values to show from json, but it’ll be similar to the one below:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/picture"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"/>

            <TextView
                android:id="@+id/gender"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"/>

            <TextView
                android:id="@+id/company"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"/>

            <TextView
                android:id="@+id/email"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"/>

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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/tags"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layoutManager="android.support.v7.widget.GridLayoutManager"
        app:spanCount="4"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/friends"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layoutManager="android.support.v7.widget.GridLayoutManager"
        app:spanCount="2"/>
</LinearLayout>

We are using ButterKnife’s binding annotations to bind this views to our activity or fragment class to fill them after a successful service response, and how we implement these binding lines was simply splitting the screen and wrote them down one by one like:

@BindView(R.id.friends)
RecyclerView friends;

This process was the second time-consuming boring implementing part for us, then we discovered beauty, Android View Binding (AVB) Code Generator that takes XML layout as an input and generates the binding code with several options like for activities, fragments or butterknife.

So without writing a piece of code I’m able to get following, simply:

/** ButterKnife Code **/
@BindView(R.id.picture)
ImageView picture;
@BindView(R.id.name)
TextView name;
@BindView(R.id.gender)
TextView gender;
@BindView(R.id.company)
TextView company;
@BindView(R.id.email)
TextView email;
@BindView(R.id.phone)
TextView phone;
@BindView(R.id.tags)
android.support.v7.widget.RecyclerView tags;
@BindView(R.id.friends)
android.support.v7.widget.RecyclerView friends;
 /** ButterKnife Code **/

Like pojo generating, there are few focus points for this tool too:

  • It won’t generate the binding code if the view does not have an id
  • It’s not that smart to understand “include” views as it is expected.

And Voilà! We have data classes, binding views implemented and can easily dive into implementing logic.

Thanks for reading, and I hope you’re already using these kind of stuff, if you are not, have the joy of discovering them with us. If you have any comments or suggestions to help this flow improved, please do share.

Happy new year and cheers!