1

I'm working my way through Head First Android Development, and I am having an issue. I'm trying to use the following code and populate the listview with an array returned from a method in another class, but I am getting errors and don't understand why. It only seems to give me errors if I try and call the method in the new ArrayAdapter, like so....

public class ListBikeStands extends ListActivity{

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

    ListView listStands = getListView();
    ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(
            this,
            android.R.layout.simple_list_item_1,
            BikesStands.getStandName());
    listStands.setAdapter(listAdapter);
}

The class/method it's calling is as follows...

public class BikeStands {

private static OkHttpClient client = new OkHttpClient();
private static final String key = "key";
private static final String contract = "contract";


public static String getJSON(String url) throws IOException {
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    return response.body().string();
}


private static String[] getStandName() {
    String json = null;

    try {
        json = getJSON("https://api.jcdecaux.com/vls/v1/stations?contract=" + contract + "&apiKey=" + key);
    } catch (IOException e) {
        e.printStackTrace();
    }

    Gson gson = new Gson();

    DBikesStation[] bikesAPI = gson.fromJson(json, DBikesStation[].class);
    String[] bikeStands = new String[bikesAPI.length];

    for (int i = 0; i < bikesAPI.length; ++i) {
        bikeStands[i] = bikesAPI[i].getName();
    }
    ;
    return bikeStands;
}

I'm getting the following error...

java.lang.RuntimeException: Unable to start activity ComponentInfo{package......dublinbikes.ListBikeStands}: android.os.NetworkOnMainThreadException

I do have ListBikeStands as an activity in the AndroidManifest.xml.

Any help is much appreciated. Thanks.

DPPD
  • 11
  • 2
  • 1
    read the error log and u will understand u do the network opeartion on main thread. so u should check ur getJson(url) method. fetch ur data with asynctask. that s to say implement asynctask in the getJson method. – ugur Sep 10 '16 at 21:08
  • 2
    Possible duplicate of [NetworkOnMainThreadException](http://stackoverflow.com/questions/5150637/networkonmainthreadexception) – Code-Apprentice Sep 10 '16 at 21:13
  • Since you are requesting JSON data, I'd suggest taking the time to learn about Retrofit – OneCricketeer Sep 10 '16 at 21:16

3 Answers3

1

You are setting the adapter in the main thread. In the adapter, you are calling the method "getJSON()" which does some networking. However, you are not allowed to do networking on the main thread. You need to grab the JSON in a separate thread - use Asynctask for that (get the JSON in "doInBackground()" and then set the adapter in "onPostExecute()")

Graham
  • 7,431
  • 18
  • 59
  • 84
techfly
  • 1,826
  • 3
  • 25
  • 31
1

Your usage of OkHTTPClient is not quite correct. You've made a synchronous request on the UI Thread, therefore your error.

public static String getJSON(String url) throws IOException {
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    return response.body().string();
}

In order to make an asynchronous request, in the background, try something like so. Pass in a Callback and use enqueue.

Important note: If you ever think you need return in an asynchronous method, that's typically not correct. See that I changed the method to a void type. Callbacks are how to return variables.

public static void getJSON(String url, Callback callback) throws IOException {
    Request request = new Request.Builder().url(url).build();
    client.newCall(request).enqueue(callback);
}

To call this, define the callback - Shown as a variable here, but can also be in-lined into the method call like a View.OnClickListner would be for a button.

You should define this inside the Activity class, since you will be updating the adapter.

final Callback bikeStandCallback = new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        String body = response.body().string();

        Gson gson = new Gson();

        DBikesStation[] bikesAPI = gson.fromJson(body, DBikesStation[].class);

        for (int i = 0; i < bikesAPI.length; ++i) {
            adapter.add(bikesAPI[i].getName());
        }
    }
};

And pass that in when you need it.

public class ListBikeStands extends ListActivity {

     private ListView listStands;
     private ArrayAdapter<String> listAdapter;

     // TODO: Define above bikeStandCallback here

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

        listStands = getListView();
        ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(
                this,
                android.R.layout.simple_list_item_1);
        listStands.setAdapter(listAdapter);

        // TODO: Get url value
        BikeStands.getJSON(url, bikeStandCallback);
    }
}

And, in my opinion, this is very flexible in the fact that you can define multiple callbacks for various data, call BikeStands.getJSON again with a different parameter, then do whatever you want with that request.

Overall, this is somewhat what Retrofit does, except the Gson converter can be added on for you.

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
0

The error you're getting is thrown not while you're populating the adapter but because you're trying to do network operation on the main thread.

You have to do network operation on a different thread, you could use AsyncTask, you could find other information about that error in this question

Networking on main thread is forbidden because is usually asynchronous and resources intensive, so it could generate an ANR (Application not responding)

Community
  • 1
  • 1
Marco
  • 705
  • 8
  • 28