Android Networking Bare Skin: Understanding JPost

Android Networking Bare Skin: Understanding JPost

Android networking is typically very involved if you try to implement bottom-to-top. So, we rely on third party libraries. But never understand what’s underneath it. In this example I have taken the very foundation of networking java.net package to build netwoking framework that is powered by JPost, a class communiction framework.

JPost

This is a Java / android library facilitating modular, asynchronous and controlled messaging between classes. The main features of this library are as listed below:

  1. In contrast to the existing pub-sub libraries, it hold the subscribers with weakreference. Thus it doesn’t create memory leaks.
  2. Single message can be sent to selected subscribes. This avoids the problem of event getting received at undesirable places. Thus minimising the chances of abnormal application behaviour.
  3. The subscriber addition can be controlled by using private channels. It minimises the chances of adding subscribes by mistake to receive undesirable messages.
  4. It is a tiny library < 55kb . Thus not affecting the application overall size.
  5. It facilitates synchronous as well as asynchronous message delivery and processing.
  6. It provides a mechanism to run code asynchronously.

What is that we will build?

We will build an android application that make network calls to get list of repositories of an user and list them in a ListView. The screenshot of the result is shown below.

Android Networking Bare Skin: Understanding JPost

If you have been following my tutorials, you would know that we follow a step oriented approach to build things up. One step at a time. Stick to your seat for half an hour, to go through a journey deep into coding.

Are you ready?

Let’s start building:

Step 1:

Create an Android Studio project and select “Empty Activity” template.

Now Get things to cook the code.

Import two libraries in the build.gradle of the app’s module:

dependencies {
    compile 'com.mindorks:android-jpost:0.0.4'
    compile 'com.google.code.gson:gson:2.7'
}

Notes:

  1. Gson is a very popular JSON parsing library. It helps convert an object into a json object and vice-versa.
  2. JPost is a class communication library as explained above.

Step 2:

Create a layout for the MainActivity: res/main/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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="ali.jpostnetworking.test.jpostnetworking.MainActivity">
    <ListView
        android:id="@+id/repoListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>
</LinearLayout>

Notes:

  1. It has a ListView to show the items in a vertical list.

Step 3:

Similarly create the list item view layout: res/layout/repo_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp"
        android:gravity="center|left">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp"
            android:minWidth="60dp"
            android:text="@string/repo_id"
            />
        <TextView
            android:id="@+id/repoIdTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp"
        android:gravity="center|left">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp"
            android:minWidth="60dp"
            android:text="@string/repo_name"
            />
        <TextView
            android:id="@+id/repoNameTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp"
        android:minWidth="60dp"
        android:gravity="center|left">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp"
            android:minWidth="60dp"
            android:text="@string/repo_url"
            />
        <TextView
            android:id="@+id/repoUrlTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="5dp"
        android:gravity="center|left">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp"
            android:minWidth="60dp"
            android:text="@string/repo_size"
            />
        <TextView
            android:id="@+id/repoSizeTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="14sp"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>
</LinearLayout>

Notes:

  1. It contains 4 row views to display 4 properties from the api response.

Step 4:

Create a class Applications that extends android.app.Application . We want to do an initialization of the networking framework, that we are building. So, we need to define it when the application starts.

Also put android:name=”.Application” (inside <application> Tag) in the AndroidManifest.xml so that the app knows, it has to execute this class when it runs.

public class Application extends android.app.Application {

    @Override
    public void onCreate() {
        super.onCreate();
        ApiHandler.init();
    }
}

Notes:

  1. ApiHandler class we will be defining in a moment.

Step 5:

Create a data model class to be constructed from json response.

public class GitRepo {

    @SerializedName("id")
    @Expose
    private Integer id;

    @SerializedName("name")
    @Expose
    private String name;

    @SerializedName("html_url")
    @Expose
    private String url;

    @SerializedName("size")
    @Expose
    private Integer size;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Integer getSize() {
        return size;
    }

    public void setSize(Integer size) {
        this.size = size;
    }
}

Notes:

  1. “@SerializedName” : Is an annotation from gson library that maps the variable to the json key.
  2. “@Expose” : It is an annotation from gson library. It makes only the variable with expose to be available for parsing.

Step 6:

Create a class GitRepoMsg to be used as a message to pass the parsed json response on the channel that we will define subsequently.

public class GitRepoMsg {

    private List<GitRepo> gitRepoList;

    public GitRepoMsg(List<GitRepo> gitRepoList) {
        this.gitRepoList = gitRepoList;
    }

    public List<GitRepo> getGitRepoList() {
        return gitRepoList;
    }

    public void setGitRepoList(List<GitRepo> gitRepoList) {
        this.gitRepoList = gitRepoList;
    }
}

Notes:

  1. This class contains a list of GitRepo objects that is parsed from the api response using gson.

Step 7:

Create RepoListAdapter, a listview adapter to populate the listview.

public class RepoListAdapter extends BaseAdapter {

    private List<GitRepo> gitRepoList;
    private Context context;

    public RepoListAdapter(Context context, List<GitRepo> gitRepoList) {
        this.context  = context;
        this.gitRepoList = gitRepoList;
    }

    @Override
    public int getCount() {
        return gitRepoList.size();
    }

    @Override
    public Object getItem(int position) {
        return gitRepoList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        final ViewHolder holder;
        if(convertView == null){
            convertView = LayoutInflater.from(context).inflate(R.layout.repo_list_item,parent,false);
            holder = new ViewHolder();
            convertView.setTag(holder);
        }else{
            holder=(ViewHolder)convertView.getTag();
        }

        holder.repoIdTxt = (TextView)convertView.findViewById(R.id.repoIdTxt);
        holder.repoNameTxt = (TextView)convertView.findViewById(R.id.repoNameTxt);
        holder.repoUrlTxt = (TextView)convertView.findViewById(R.id.repoUrlTxt);
        holder.repoSizeTxt = (TextView)convertView.findViewById(R.id.repoSizeTxt);

        GitRepo repo = gitRepoList.get(position);
        holder.repoIdTxt.setText(String.valueOf(repo.getId()));
        holder.repoNameTxt.setText(repo.getName());
        holder.repoUrlTxt.setText(repo.getUrl());
        holder.repoSizeTxt.setText(String.valueOf(repo.getSize()));

        return convertView;
    }

    private class ViewHolder{
        TextView repoIdTxt;
        TextView repoNameTxt;
        TextView repoUrlTxt;
        TextView repoSizeTxt;
    }

    public void setGitRepoList(List<GitRepo> gitRepoList) {
        this.gitRepoList = gitRepoList;
        notifyDataSetChanged();
    }
}

Notes:

  1. The adapter is passed a list of GitRepo objects which it used to populate the list item views.
  2. setGitRepoList method re-initializes the list and call to repopulate the list item views with new data.

Step 8:

Now the most fun part

Create a class ApiHandler to manage the api calls. Let’s see this class first and then will try to understand it.

public class ApiHandler{

    public static String GIT_REPO_URL = "https://api.github.com/users/mralexgray/repos";

    private static final int API_CHANNEL_ID = 1000;
    private static ApiHandler apiHandler;

    public static void init(){
        apiHandler = new ApiHandler();
    }

    public ApiHandler() {
        try {
            JPost.getBroadcastCenter().createPrivateChannel(this, API_CHANNEL_ID);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void doGitRepoGetApiCall(String url){
        try {
            JPost.getBroadcastCenter().broadcastAsync(apiHandler, API_CHANNEL_ID, url);
        }catch (JPostNotRunningException e){
            e.printStackTrace();
        }
    }

    @OnMessage(channelId = API_CHANNEL_ID)
    public void processGitRepoGet(String url) {
        try {
            URL obj = new URL(url);
            HttpURLConnection con = (HttpURLConnection) obj.openConnection();
            con.setRequestMethod("GET");

            int responseCode = con.getResponseCode();
            Log.d("Debug", "Response Code : " + responseCode);

            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(con.getInputStream()));
                String inputLine;
                StringBuffer response = new StringBuffer();

                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }
                in.close();

                Log.d("Debug", "Response : " + response);
                GsonBuilder builder = new GsonBuilder();
                Gson gson = builder.create();

                GitRepo[] gitRepoArray = gson.fromJson(response.toString(), GitRepo[].class);
                JPost.getBroadcastCenter().broadcastAsync(new GitRepoMsg(Arrays.asList(gitRepoArray)));
            }
        }catch (MalformedURLException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }catch (JPostNotRunningException e){
            e.printStackTrace();
        }
    }
}

Notes:

  1. GIT_REPO_URL is a defines to facilitate the referencing to the api endpoint from any class.
  2. API_CHANNEL_ID is the int id, we want to attach to a private channel we are creating through JPost. JPost provides three kinds of channel. Default, Public and Private, Private channel is used to communicate with the subscribers in a controlled way. Only Creator can add subscribers on this channel and only subscribers of this channel can post messages over it. For more details click here JPost.
  3. We have init method to instantiate the ApiHandler class.
  4. The ApiHandler() constructor creates a private channel with channel id.
  5. doGitRepoGetApiCall method provides an interface to make api call. It sends a message over the api channel to make the api call and process it in a non UI thread.
  6. @OnMessage” is from JPost. It binds a method to receive message on the channel.
  7. processGitRepoGet method creates HttpURLConnection by opening the connection and makes get request on the provided url. It gets the api response in an input stream. From the input stream it creates a StringBuffer to be used to parse the json.
  8. Through GsonBuilder we gets a gson object and use it’s fromJson method to get an array of parsed GitRepo objects. The array is converted into List using Arrays.asList method.
  9. The GitRepoMsg is instantiated and send on the DEFAULT GLOBAL channel of the JPost. Default global channel sends and receives messages for all the subscribes.
  10. See https://github.com/janishar/JPost for documnetation on JPost.

Step 9:

Define the MainActivity class

public class MainActivity extends AppCompatActivity {

    private ListView repoListView;
    private RepoListAdapter repoListAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        repoListView = (ListView) findViewById(R.id.repoListView);
        repoListAdapter = new RepoListAdapter(getApplicationContext(), new ArrayList<GitRepo>());
        repoListView.setAdapter(repoListAdapter);
        try {
            JPost.getBroadcastCenter().addSubscriber(this);
        }catch (Exception e){
            e.printStackTrace();
        }
        ApiHandler.doGitRepoGetApiCall(ApiHandler.GIT_REPO_URL);
    }

    @OnUiThread
    @OnMessage
    private void onGitRepoList(GitRepoMsg msg){
        if(msg.getGitRepoList() != null) {
            repoListAdapter.setGitRepoList(msg.getGitRepoList());
        }
    }
}

Notes:

  1. MainActivity subscribes to JPost’s Default Channel using addSubscriber(object) method
  2. @OnUiThread” : Annotation is provided by JPost to run the code on the Android UI thread.
  3. It receives the response message through the default global channel and updates the listview

That’s All.

You can appreciate the JPost for making such a networking architecture so simple and robust. This was a guideline example. The path to a complete system development is left as an exercise.

Coder’s Rock