Android:Network

From Juneday education
Jump to: navigation, search

Introduction

Network access is used in many of today's apps. Most common is to use http/https, two protocols which are the basis for the internet or rather the world wide web. Networking in Android has not been straight forward since you as a developer need to take into concern the user experience and not let the Activity hang during network download/upload as well as writing quite lot of code.

Volley is a framework to ease up networking among other things. On the Volley pages you can find its benefitts listed like this:

  • Automatic scheduling of network requests.
  • Multiple concurrent network connections.
  • Transparent disk and memory response caching with standard HTTP cache coherence.
  • Support for request prioritization.
  • Cancellation request API. You can cancel a single request, or you can set blocks or scopes of requests to cancel.
  • Ease of customization, for example, for retry and backoff.
  • Strong ordering that makes it easy to correctly populate your UI with data fetched asynchronously from the network.
  • Debugging and tracing tools.

The source code used in this page can be found here: progund/android-examples/volleyex/

Videos

Android:Volley (Full playlist) | Android - Volley (live video)


Network and Web

We suggest you check out Introduction to network and Introduction to web if you want to get familiar with network and web.

JSON Videos

We're using the org.json package in Android when writing JSON code. If you want information on how to use JSON you can check these videos out:

Java - Parsing with org.json

Java - Creating with org.json

The JSON videos above are part of our book Introduction to web.

Network using HttpClient

Network using Volley

In the following sections we will use a simple app as an example. The app should download member (let's assume in an organisation) in JSON format. The members, when extracted from JSON, should be presented in a ListView. We provide three rather similar ways to acomplish the task of getting data and presenting it.

Here's a picture of the app: ListView presenting data downloaded with Volley

Preparing to use Network

Add the following to your Manifest file

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Our file AndroidManifest.xml looks like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="se.juneday.substitutescheduler">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Preparing to use Volley

First of all we need to make sure we can use Volley in our code. Add the following to file file build.gradle (Module:app) in the section dependencies:

    compile 'com.android.volley:volley:1.1.0'

Our build.gradle now looks like this:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "se.juneday.substitutescheduler"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    compile 'com.android.volley:volley:1.1.0'

}

Note: after you've added this code you may have to rebuild the project.

Let's have a look at the JSON data our app downloads:

[
    {
        "name": "Henrik Sandklef",
        "email": "nobrain@juneday.se"
    },
    {
        "name": "Rikard Fröberg",
        "email": "gobrain@juneday.se"
    },
    {
        "name": "Smarty Pants",
        "email": "smrt@juneday.se"
    }
]

The data above can be found at: https://github.com/progund/android-examples/blob/master/common-data/members.json

Our Member class looks like this:

package se.juneday.volleyex.volleyexample;


public class Member {

  private String name;
  private String email;

  public Member(String name, String email) {
    this.name = name;
    this.email = email;
  }

  public String name() {
    return name;
  }

  public String email(){
    return email;
  }

  @Override
  public String toString() {
    return name + (email!=null?"<" + email +">":"");
  }

}

Volley code inside your Activity

The Activity (VolleyActivity) makes use of ListView and ArrayAdapter to present the Member data.

We will not spend time on explaining ListView and ArrayAdapter more than simply saying we're using them as they are. The interesting parts of the code are the following two following methods:

  private List<Member> jsonToMembers(JSONArray array) {
    List<Member> memberList = new ArrayList<>();
    for (int i = 0; i < array.length(); i++) {
      try {
        JSONObject row = array.getJSONObject(i);
        String name = row.getString("name");
        String email = row.getString("email");

        Member m = new Member(name, email);
        memberList.add(m);
      } catch (JSONException e) {
        ; // is ok since this is debug
      }
    }
    return memberList;
  }

  // The code below is "slightly" (nudge nudge) based on:
  //   https://developer.android.com/training/volley/request.html
  private void getMembers() {

    RequestQueue queue = Volley.newRequestQueue(this);

    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
        Request.Method.GET,
        Settings.url,
        null,
        new Response.Listener<JSONArray>() {

          @Override
          public void onResponse(JSONArray array) {
            members = jsonToMembers(array);
            resetListView();
            ActivitySwitcher.showToast(me, "Members updated");

          }
        }, new Response.ErrorListener() {

      @Override
      public void onErrorResponse(VolleyError error) {
        Log.d(LOG_TAG, " cause: " + error.getCause().getMessage());
        ActivitySwitcher.showToast(me, "Members update failed");
      }
    });

    // Add the request to the RequestQueue.
    queue.add(jsonArrayRequest);

  }

The complete source code of this file can be found at: https://github.com/progund/android-examples/blob/master/volleyex/app/src/main/java/se/juneday/volleyex/volleyexample/VolleyActivity.java

The code above is looking very much like the code at Android's page on downloading JSON with Volley which is no coincident since we have used their example as basis for our code. We haven't changed a lot from the Android example so there's not a lot to explain but we would like to spend some time on the following code:

    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
        Request.Method.GET,
        Settings.url,
        null,
        new Response.Listener<JSONArray>() {

          @Override
          public void onResponse(JSONArray array) {
            members = jsonToMembers(array);
            resetListView();
            ActivitySwitcher.showToast(me, "Members updated");

          }
        }, new Response.ErrorListener() {

      @Override
      public void onErrorResponse(VolleyError error) {
        Log.d(LOG_TAG, " cause: " + error.getCause().getMessage());
        ActivitySwitcher.showToast(me, "Members update failed");
      }
    });

The code above creates a JsonArrayRequest by creating an anonymous inner class (see Chapter:Interfaces - Creating an anonymous class) that implements onResponse and onErrorResponse which are two methods executed either on successful download or when an error occurs. In the onResponse we transform the JSON data to a List of Memberss and use that list in our listview. The method resetListView is updating the ListView which it can since the Volley code is located in the Activity class. In the coming two sections we will at two different ways to move the Volley code out of the Activity.


In case you wonder about the class ActivitySwitcher it is a class we've written to deal with code shared between our classes. See below for more information

Volley code outside the Activity

Let's assume we want to move the Volley code outside the Activity. One reason for this could be that we do not want any coupling between Volley and the Activity. The app is the same but we are now going to look at another Activity, SemiSeparateActivity. The difference this time is that the Volley code is located in a separate class, SemiVolleyMember. How on earth could this class update the gui in the Activity? It can't But we can pass a reference to our Activity as an argument to the SemiVolleyMember constructor. Using this reference we can invoke a dedicated method in the Activity. This is in our opinion too tight coupling between the SemiVolleyMember and the Activity. And what if we want to reuse the SemiVolleyMember for other purposes? So let's be a bit more general.

Instead of the interface knowing that there is a method in an abject passed to it as a reference we could twist things around a bit. We could, and actually will, let the SemiVolleyMember declare an interface called MemberChangeListener. This interface specifies a method void onMemberChangeList(List<Member> members); which need to be implemented. Anyone who's interested in getting notified, and passed a fresh list of Members, only have to implement the interface and pass it to the SemiVolleyMember class. We have decided to use the Design Pattern (see our book More_programming_with_Java) Singleton. So the reference to the object implementing the interface onMemberChangeList need to be passed as an argument to the method getInstance in the SemiVolleyMember class.

The Volley code looks a lot like in the previous section but is now, as we said, in a separate class:

package se.juneday.volleyex.volleyexample;

import android.content.Context;
import android.util.Log;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.Volley;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import se.juneday.volleyex.volleyexample.VolleyMember.MemberChangeListener;

public class SemiVolleyMember {

  private static final String LOG_TAG = SemiVolleyMember.class.getName();

  private static SemiVolleyMember volleyMember;
  private Context context;
  MemberChangeListener listener;

  public static synchronized SemiVolleyMember getInstance(Context context, MemberChangeListener listener) {
    if (volleyMember == null) {
      volleyMember = new SemiVolleyMember(context, listener);
    }
    return volleyMember;
  }

  private SemiVolleyMember(Context context, MemberChangeListener listener) {
    this.listener = listener;
    this.context = context;
  }

  private List<Member> jsonToMembers(JSONArray array) {
    List<Member> memberList = new ArrayList<>();
    for (int i = 0; i < array.length(); i++) {
      try {
        JSONObject row = array.getJSONObject(i);
        String name = row.getString("name");
        String email = row.getString("email");

        Member m = new Member(name, email);
        memberList.add(m);
      } catch (JSONException e) {
        ; // is ok since this is debug
      }
    }
    return memberList;
  }

  // The code below is "slightly" (nudge nudge) based on:
  //   https://developer.android.com/training/volley/request.html
  public void getMembers() {
    RequestQueue queue = Volley.newRequestQueue(context);

    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
        Request.Method.GET,
        Settings.url,
        null,
        new Response.Listener<JSONArray>() {

          @Override
          public void onResponse(JSONArray array) {
            List<Member> members = jsonToMembers(array);
            listener.onMemberChangeList(members);
          }
        }, new Response.ErrorListener() {

      @Override
      public void onErrorResponse(VolleyError error) {
        Log.d(LOG_TAG, " cause: " + error.getCause().getMessage());
      }
    });

    // Add the request to the RequestQueue.
    queue.add(jsonArrayRequest);
  }

  /******************************************
   MemberChangeListener
   ******************************************/

  public interface MemberChangeListener {
    void onMemberChangeList(List<Member> members);
  }

}

Note that the code above contains the declaration of the interface we discussed above. We would like to spend some time on the code invoked on successful download of SON data:

          @Override
          public void onResponse(JSONArray array) {
            List<Member> members = jsonToMembers(array);
            listener.onMemberChangeList(members);
          }

The code above invokes the method onMemberChangeList, which is the method declared in the interface MemberChangeListener. We can invoke this method associated with the object passed as an argument to the method getInstance. This means that the Volley class will invoke a method in another object. It is this object's responsibility to do something useful. So who passed a MemberChangeListener reference to the Volley class?

In the SemiSeparateActivity's method onCreate the following lines (among other) are of interest:

    listener = new MemberChangeListener() {
      @Override
      public void onMemberChangeList(List<Member> members) {
        resetListView(members);
        ActivitySwitcher.showToast(me, "Members updated");
      }
    };

    semiVolleyMember = SemiVolleyMember.getInstance(this, listener);

Note: semiVolleyMember is declared as private SemiSeparateActivity me;

In the code above we create an anonymous inner class (see Chapter:Interfaces - Creating an anonymous class ) from the interface onMemberChangeList which we discussed above. This means that it is the method above that will be invoked by the Volley class on successful download. This method is in the Activity class and we can therefor update the gui.

Volley code outside the Activity using Observer

In this section we will be a bit more flexible by letting several objects getting notified on successful download. We make use of the Design pattern Observer. In our case we're leaving the idea of passing the reference to an object via the method getInstance and instead provide a method, addMemberChangeListener, that can be used to register for notification on successful download. In short the differences from the previous solution is that the Volley class, VolleyMember, are:

  • multiple listeners are possible (private List<MemberChangeListener> listeners; is used to keep track of the listeners)
  • objects can register to get notified (using addMemberChangeListener)
  • each listener is getting notified (using onMemberChangeList)

The interesting parts of the class VolleyMember looks like this:

  .....
  public void getMembers() {
    RequestQueue queue = Volley.newRequestQueue(context);

    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
        Request.Method.GET,
        Settings.url,
        null,
        new Response.Listener<JSONArray>() {

          @Override
          public void onResponse(JSONArray array) {
            List<Member> members = jsonToMembers(array);
            for (MemberChangeListener m : listeners) {
              m.onMemberChangeList(members);
            }
          }
        }, new Response.ErrorListener() {

      @Override
      public void onErrorResponse(VolleyError error) {
        Log.d(LOG_TAG, " cause: " + error.getCause().getMessage());
      }
    });

    // Add the request to the RequestQueue.
    queue.add(jsonArrayRequest);
  }
  .....
    /******************************************
   MemberChangeListener
   ******************************************/

  private List<MemberChangeListener> listeners;

  public interface MemberChangeListener {
    void onMemberChangeList(List<Member> members);
  }

  public void addMemberChangeListener(MemberChangeListener l) {
    listeners.add(l);
  }

How do we register to get notified then? Let's have a look at that code in the coresponding Activity, SeparateActivity:

 ...
  private SeparateActivity me; // an instance variable used to store 'this'
 ...
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_volley);

    members = new ArrayList<>();
    resetListView(members);

    me = this;

    // register to listen to member updates in VolleyMember
    VolleyMember.getInstance(this).addMemberChangeListener(new MemberChangeListener() {
      @Override
      public void onMemberChangeList(List<Member> members) {
        resetListView(members);
        ActivitySwitcher.showToast(me, "Members updated");
      }
    });

    ((TextView)findViewById(R.id.label)).setText(LOG_TAG);
  }

In the code above we:

  • store this in a variable (me) so we can pass it to the method addMemberChangeListener. We can't pass this at the point in the code since that code belongs to an object of an anonymous inner class and then this refers to that specific object.
  • implement the interface MemberChangeListener
  • in the method onMemberChangeList we update the gui. This method is invoked by the VolleyMember object to notify us on a successful download

Helper classes in this example

ActivitySwitcher

This is a small class we've written to make use in our Activities. We don'r expect any Nobel prize for this one.

package se.juneday.volleyex.volleyexample;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

public class ActivitySwitcher {

  private static final String LOG_TAG = ActivitySwitcher.class.getSimpleName();

  public static void switchActivity(Class c, Activity context) {
    Intent intent = new Intent(context, c);
    context.startActivity(intent);
  }

  public static void switchToSemiSeparateActivity(Activity context) {
    switchActivity(SemiSeparateActivity.class, context);
  }

  public static void switchToSeparateActivity(Activity context) {
    switchActivity(SeparateActivity.class, context);
  }

  public static void switchToVolleyActivity(Activity context) {
    switchActivity(VolleyActivity.class, context);
  }

  public static void showToast(Context context, String msg) {
    Log.d(LOG_TAG, " showToast: " + msg);
    int duration = Toast.LENGTH_SHORT;
    Toast toast = Toast.makeText(context, msg, duration);
    toast.show();
  }


}

The class can be downloaded from here: ActivitySwitcher.java