The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Internet Access

The expectation is that most, if not all, Android devices will have built-in Internet access. That could be WiFi, cellular data services (EDGE, 3G, etc.), or possibly something else entirely. Regardless, most people — or at least those with a data plan or WiFi access — will be able to get to the Internet from their Android phone.

Not surprisingly, the Android platform gives developers a wide range of ways to make use of this Internet access. Some offer high-level access, such as the integrated WebKit browser component (WebView) we saw in an earlier chapter. If you want, you can drop all the way down to using raw sockets. Or, in between, you can leverage APIs — both on-device and from 3rd-party JARs — that give you access to specific protocols: HTTP, XMPP, SMTP, and so on.

The emphasis of this book is on the higher-level forms of access: the WebKit component and Internet-access APIs, as busy coders should be trying to reuse existing components versus rolling one’s own on-the-wire protocol wherever possible.

DIY HTTP

In many cases, your only viable option for accessing some Web service or other HTTP-based resource is to do the request yourself. The Google-endorsed API for doing this nowadays in Android is to use the classic java.net classes for HTTP operation, centered around HttpUrlConnection. There is quite a bit of material on this already published, as these classes have been in Java for a long time. The focus here is in showing how this works in an Android context.

Note, however, that you may find it easier to use some HTTP client libraries that handle various aspects of the Internet access for you, as will be described later in this chapter.

A Sample Usage of HttpUrlConnection

This chapter walks through several implementations of a Stack Overflow client application. The app has a single activity, with a single ListFragment. The app will load the latest block of Stack Overflow questions tagged with android, using the Stack Exchange public API. Those questions will be shown in the list, and tapping on a question will bring up the Web page for that question in the user’s default Web browser.

All implementations of the app have the same core UI logic. What differs is in how each handles the Internet access. In this section, we will take a look at the Internet/HURL sample project, which uses HttpUrlConnection to retrieve the questions from the Stack Exchange Web service API.

Asking Permission

To do anything with the Internet (or a local network) from your app, you need to hold the INTERNET permission. This includes cases where you use things like WebView — if your process needs network access, you need the INTERNET permission.

Hence, the manifest for our sample project contains the requisite <uses-permission> declaration:


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

Creating Your Data Model

The Stack Exchange Web service API returns JSON in response to various queries. Hence, we need to create Java classes that mirror that JSON structure. In particular, many of the examples will be using Google’s Gson to populate those data models automatically based upon its parsing of the JSON that we receive from the Web service.

In our case, we are going to use a specific endpoint of the Stack Exchange API, referred to as /questions after the distinguishing portion of the path. The documentation for this endpoint can be found in the Stack Exchange API documentation.

We will examine the URL for the endpoint a bit later in this section.

The results we get for issuing a GET request for the URL is a JSON structure (here showing a single question, to keep the listing short):


{
  "items": [
    {
      "question_id": 17196927,
      "creation_date": 1371660594,
      "last_activity_date": 1371660594,
      "score": 0,
      "answer_count": 0,
      "title": "ksoap2 failing when in 3G",
      "tags": [
        "android",
        "ksoap2",
        "3g"
      ],
      "view_count": 2,
      "owner": {
        "user_id": 773259,
        "display_name": "SparK",
        "reputation": 513,
        "user_type": "registered",
        "profile_image": "http://www.gravatar.com/avatar/511b37f7c313984e624dd76e8cb9faa6?d=identicon&r=PG",
        "link": "http://stackoverflow.com/users/773259/spark"
      },
      "link": "http://stackoverflow.com/questions/17196927/ksoap2-failing-when-in-3g",
      "is_answered": false
    }
  ],
  "quota_remaining": 9991,
  "quota_max": 10000,
  "has_more": true
}

NOTE: Some of the longer URLs will word-wrap in the book, but they are on a single line in the actual JSON. Honest.

We get back a JSON object, where our questions are found under the name of items. items is a JSON array of JSON objects, where each JSON object represents a single question, with fields like title and link. The question JSON object has an embedded owner JSON object with additional information.

We do not necessarily need all of this information. In fact, for this first version of the sample, all we really need are the title and link of each entry in the items array.

The key is that, by default, the data members in our Java data model must exactly match the JSON keys for the JSON objects.

So, we have an Item class, representing the information from an individual entry in the items array:

package com.commonsware.android.hurl;

public class Item {
  String title;
  String link;
  
  @Override
  public String toString() {
    return(title);
  }
}

However, our Web service does not return the items array directly. items is the key in a JSON object that is the actual JSON returned by Stack Exchange. So, we need another Java class that contains the data members we need from that outer JSON object, here named SOQuestions (for lack of a better idea for a name…):

package com.commonsware.android.hurl;

import java.util.List;

public class SOQuestions {
  List<Item> items;
}

Having an items data member that is a List of Item tells GSON that we are expecting the JSON object to be used for SOQuestions to have a JSON array, named items, where each element in that array should get mapped to Item objects.

A Thread for Loading

We need to do the network I/O on a background thread, so we do not tie up the main application thread. To that end, the sample app has a LoadThread that loads our questions:

package com.commonsware.android.hurl;

import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import com.google.gson.Gson;
import de.greenrobot.event.EventBus;

class LoadThread extends Thread {
  static final String SO_URL=
      "https://api.stackexchange.com/2.1/questions?"
          + "order=desc&sort=creation&site=stackoverflow&tagged=android";

  @Override
  public void run() {
    try {
      HttpURLConnection c=
          (HttpURLConnection)new URL(SO_URL).openConnection();

      try {
        InputStream in=c.getInputStream();
        BufferedReader reader=
            new BufferedReader(new InputStreamReader(in));
        SOQuestions questions=
            new Gson().fromJson(reader, SOQuestions.class);

        reader.close();
        
        EventBus.getDefault().post(new QuestionsLoadedEvent(questions));
      }
      catch (IOException e) {
        Log.e(getClass().getSimpleName(), "Exception parsing JSON", e);
      }
      finally {
        c.disconnect();
      }
    }
    catch (Exception e) {
      Log.e(getClass().getSimpleName(), "Exception parsing JSON", e);
    }
  }
}

LoadThread:

QuestionsLoadedEvent is a simple wrapper around an SOQuestions instance, serving as an event class for use with EventBus:

package com.commonsware.android.hurl;

public class QuestionsLoadedEvent {
  final SOQuestions questions;
  
  QuestionsLoadedEvent(SOQuestions questions) {
    this.questions=questions;
  }
}

A Fragment for Questions

The sample app has a QuestionsFragment that should display these loaded questions:

package com.commonsware.android.hurl;

import android.app.ListFragment;
import android.os.Bundle;
import android.text.Html;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;
import de.greenrobot.event.EventBus;

public class QuestionsFragment extends ListFragment {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setRetainInstance(true);
    new LoadThread().start();
  }

  @Override
  public void onResume() {
    super.onResume();
    EventBus.getDefault().register(this);
  }

  @Override
  public void onPause() {
    EventBus.getDefault().unregister(this);
    super.onPause();
  }

  @Override
  public void onListItemClick(ListView l, View v, int position, long id) {
    Item item=((ItemsAdapter)getListAdapter()).getItem(position);

    EventBus.getDefault().post(new QuestionClickedEvent(item));
  }

  public void onEventMainThread(QuestionsLoadedEvent event) {
    setListAdapter(new ItemsAdapter(event.questions.items));
  }

  class ItemsAdapter extends ArrayAdapter<Item> {
    ItemsAdapter(List<Item> items) {
      super(getActivity(), android.R.layout.simple_list_item_1, items);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      View row=super.getView(position, convertView, parent);
      TextView title=(TextView)row.findViewById(android.R.id.text1);

      title.setText(Html.fromHtml(getItem(position).title));

      return(row);
    }
  }
}

In onCreate(), we mark that this fragment should be retained and fork the LoadThread. Hence, once we have our questions, our retained fragment will hold onto that model data for us, and we avoid duplicating the LoadThread if a configuration change occurs sometime after our fragment was initially created.

In onResume() and onPause(), we register and unregister from the EventBus. Our onEventMainThread() method will be called when the QuestionsLoadedEvent is raised by LoadThread, and there we hold onto the loaded questions and populate the ListView. We use an ItemsAdapter, which knows how to render an Item as a simple ListView row showing the question title. The ItemsAdapter uses Html.fromHtml() to populate the ListView rows, not because Stack Overflow hands back titles with HTML tags, but because Stack Overflow hands back titles with HTML entity references, and Html.fromHtml() should handle many of those.

And, in onListItemClick(), we find the Item associated with the row that the user clicked upon, then raise a QuestionClickedEvent to let somebody know that the user clicked on that row. The QuestionClickedEvent class is a simple wrapper around an Item to serve as an event class for use with EventBus:

package com.commonsware.android.hurl;

public class QuestionClickedEvent {
  final Item item;
  
  QuestionClickedEvent(Item item) {
    this.item=item;
  }
}

An Activity for Orchestration

MainActivity sets up the fragment in onCreate(), registers and unregisters for the event bus in onResume() and onPause(), and handles the click events in onEventMainThread():

package com.commonsware.android.hurl;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import de.greenrobot.event.EventBus;

public class MainActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
      getFragmentManager().beginTransaction()
                          .add(android.R.id.content,
                               new QuestionsFragment()).commit();
    }
  }

  @Override
  public void onResume() {
    super.onResume();
    EventBus.getDefault().register(this);
  }

  @Override
  public void onPause() {
    EventBus.getDefault().unregister(this);
    super.onPause();
  }

  public void onEventMainThread(QuestionClickedEvent event) {
    startActivity(new Intent(Intent.ACTION_VIEW,
                             Uri.parse(event.item.link)));
  }
}

Hence, MainActivity is serving in an orchestration role. QuestionsFragment is a local controller, handling direct events raised by its widgets (a ListView). MainActivity is responsible for handling events that transcend an individual fragment — in this case, it starts a browser to view the clicked-upon question.

The result is a simple ListView showing questions:

HURLDemo, Showing Stack Overflow Questions
Figure 315: HURLDemo, Showing Stack Overflow Questions

What Android Brings to the Table

Google has augmented HttpUrlConnection to do more stuff to help developers. Notably:

Also, courtesy of some third-party code (OkHttp) that we will discuss shortly, HttpUrlConnection also supports the SPDY protocol for accelerating Web content distribution over SSL as of Android 4.4.

Testing with StrictMode

StrictMode, mentioned in the chapter on files, can also report on performing network I/O on the main application thread. More importantly, on Android 3.0 and higher, by default, Android will crash your app with a NetworkOnMainThreadException if you try to perform network I/O on the main application thread.

Hence, it is generally a good idea to test your app, either using StrictMode yourself or using a suitable emulator, to make sure that you are not performing network I/O on the main application thread.

What About HttpClient?

Android also contains — or used to contain – a mostly-complete copy of version 4.0.2beta of the Apache HttpClient library. Many developers use this, as they prefer the richer API offered by this library over the somewhat more clunky approach used by java.net. And, truth be told, this was the more stable option prior to Android 2.3.

There are a few reasons why this is no longer recommended, for Android 2.3 and beyond:

If you have legacy code that uses the HttpClient API, please consider using Apache’s standalone edition of HttpClient for Android.

And, if you cannot do any of that, and you are using Gradle for Android for your builds (e.g., you are using Android Studio’s default settings), you can add useLibrary 'org.apache.http.legacy' to the android closure to give you access to Android’s stock HttpClient API:


android {
    useLibrary 'org.apache.http.legacy'
    // other settings go here
}

However, usually, using a standalone edition should be reasonably practical.

For example, the Internet/HttpClient sample project is a clone of the HttpURLConnection sample from earlier in this chapter, revised to use HttpClient. More specifically, it uses the cz.msebera.android packaging of Apache HttpClient for Android:

apply plugin: 'com.android.application'

dependencies {
    compile 'de.greenrobot:eventbus:2.2.1'
    compile 'com.google.code.gson:gson:2.3'
    compile 'cz.msebera.android:httpclient:4.4.1.1'
}

android {
    compileSdkVersion 19
    buildToolsVersion "21.1.2"
}

The classes are all the same as in the equivalent Apache HttpClient code, except that org.apache is replaced by cz.msebera.android.

The code that you might have used with Android’s built-in HttpClient will not directly work on newer versions of Apache’s HttpClient, due to API differences. However, it is fairly close in most common places, requiring slight modifications, usually to improve the API.

For example, here is the LoadThread class from before, revised to use HttpClient:

package com.commonsware.android.httpclient;

import android.util.Log;
import com.google.gson.Gson;
import java.io.IOException;
import cz.msebera.android.httpclient.client.HttpClient;
import cz.msebera.android.httpclient.client.methods.HttpGet;
import cz.msebera.android.httpclient.impl.client.BasicResponseHandler;
import cz.msebera.android.httpclient.impl.client.HttpClientBuilder;
import cz.msebera.android.httpclient.impl.conn.PoolingHttpClientConnectionManager;
import de.greenrobot.event.EventBus;

class LoadThread extends Thread {
  static final String SO_URL=
      "https://api.stackexchange.com/2.1/questions?"
          + "order=desc&sort=creation&site=stackoverflow&tagged=android";

  @Override
  public void run() {
    try {
      HttpClient client=HttpClientBuilder.create()
        .setConnectionManager(
          new PoolingHttpClientConnectionManager())
        .build();
      HttpGet get=new HttpGet(SO_URL);

      try {
        String result=client.execute(get, new BasicResponseHandler());
        SOQuestions questions=
            new Gson().fromJson(result, SOQuestions.class);

        EventBus.getDefault().post(new QuestionsLoadedEvent(questions));
      }
      catch (IOException e) {
        Log.e(getClass().getSimpleName(), "Exception parsing JSON", e);
      }
    }
    catch (Exception e) {
      Log.e(getClass().getSimpleName(), "Exception parsing JSON", e);
    }
  }
}

We start by getting an HttpClient instance from an HttpClientBuilder. Particularly if you use the PoolingHttpClientConnectionManager, as shown here, the HttpClient instance is designed to be shared among multiple threads, and so could be used as a singleton. Here, we only use it once, so it is merely a local variable in the run() method.

From there, we:

HTTP via DownloadManager

The preview of this section was lost in the sofa cushions.

Using Third-Party JARs

The preview of this section is unavailable right now, but if you leave your name and number at the sound of the tone, it might get back to you (BEEEEEEEEEEEEP!).

SSL

The preview of this section is in an invisible, microscopic font.

Using HTTP Client Libraries

The preview of this section left for Hollywood to appear in a reality TV show.

Visit the Trails

The preview of this section is in an invisible, microscopic font.