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 Overflow 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 Overflow 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 Overflow 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 Overflow API, referred to as /questions after the distinguishing portion of the path. The documentation for this endpoint can be found in the Stack Overflow 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 Overflow. 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 org.greenrobot.eventbus.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.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ListFragment;
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 org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;

public class QuestionsFragment extends ListFragment {
  private boolean loadRequested=false;

  public interface Contract {
    void onQuestion(Item question);
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setRetainInstance(true);
  }

  @Override
  public void onViewCreated(@NonNull View view,
                            @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    if (!loadRequested) {
      loadRequested=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);

    ((Contract)getActivity()).onQuestion(item);
  }

  @Subscribe(threadMode = ThreadMode.MAIN)
  public void onQuestionsLoaded(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=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, so if the activity undergoes a configuration change, this fragment will stick around.

In onViewCreated(), we fork the LoadThread. Hence, once we have our questions, our retained fragment will hold onto that model data for us. To avoid duplicating the LoadThread if a configuration change occurs sometime after our fragment was initially created, we track whether or not we have already requested our data load via a loadRequested flag.

In onResume() and onPause(), we register and unregister from the EventBus. Our onQuestionsLoaded() 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 call an onQuestion() method on our hosting activity. That activity needs to implement the Contract interface, so we can call the onQuestion() method on whatever activity happens to host this fragment.

An Activity for Orchestration

MainActivity sets up the fragment in onCreate() and handles the click events in onQuestion():

package com.commonsware.android.hurl;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity
  implements QuestionsFragment.Contract {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

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

  @Override
  public void onQuestion(Item question) {
    startActivity(new Intent(Intent.ACTION_VIEW,
                             Uri.parse(question.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 294: 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 and HTTP/2 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, by default, Android will crash your app with a NetworkOnMainThreadException if you try to perform network I/O on the main application thread.

What About HttpClient?

The preview of this section was stepped on by Godzilla.

HTTP via DownloadManager

The preview of this section is sleeping in.

Using Third-Party Libraries

The preview of this section apparently resembled a Pokémon.

SSL

The preview of this section was stepped on by Godzilla.

Using HTTP Client Libraries

The preview of this section was whisked away by a shark-infested tornado.

Visit the Trails

The preview of this section is out seeking fame and fortune as the Dread Pirate Roberts.