Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Android

Using OKHttpClient without rewriting the same code repeatedly

I'm creating an app that makes multiple calls to an API. I'm using code from the "Create a weather app" tutorial as a starting point. However, I found that I was copying the code from one activity to another. I'd like to create a wrapper class for this functionality and instantiate an object whenever I needed to make another call to the API. The problem I'm running into is that my Activity code doesn't run on the same thread as my OkHttpClient wrapper class code. I get the following error:

2019-08-20 08:42:34.283 4439-4664/com.doublel.keystone E/AndroidRuntime: FATAL EXCEPTION: Thread-4 Process: com.doublel.keystone, PID: 4439 java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String org.json.JSONObject.getString(java.lang.String)' on a null object reference at com.doublel.keystone.LocationActivity$1.run(LocationActivity.java:62) at java.lang.Thread.run(Thread.java:761)

Here is my Activity code:

package com.doublel.keystone;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.doublel.keystone.model.BinLocation;
import com.doublel.keystone.model.Employee;
import com.doublel.keystone.model.JSONHTTPRequest;

import org.json.JSONException;
import org.json.JSONObject;

public class LocationActivity extends AppCompatActivity {

    public static final String TAG = LocationActivity.class.getSimpleName();

    private Employee employee;
    private String workorderId;
    private BinLocation binLocation;
    private TextView binLocationValue;
    private TextView partNumberValue;
    private TextView partDescValue;
    private TextView qtyValue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_location);

        this.binLocationValue = findViewById(R.id.binLocationValue);
        this.partNumberValue = findViewById(R.id.partNumberValue);
        this.partDescValue = findViewById(R.id.partDescValue);
        this.qtyValue = findViewById(R.id.qtyValue);

        Intent intent = getIntent();
        String employeeJSON = intent.getStringExtra("employee");
        try {
            this.employee = new Employee(new JSONObject(employeeJSON));
        } catch(JSONException e) {
            Log.e(TAG, e.getMessage());
        }
        this.workorderId = intent.getStringExtra("workorderId");

        Log.i(TAG, "Employee: " + this.employee.toString());
        Log.i(TAG, "Workorder ID: " + this.workorderId);

        String bin = intent.getStringExtra("binLocation");
        this.binLocation = new BinLocation(bin);

        Thread collectBin = new Thread(new Runnable() {
            public void run() {
                JSONHTTPRequest request = new JSONHTTPRequest("http://test.ll.com/api/getBinLocationInformation.php", JSONHTTPRequest.REQUEST_TYPE_GET, LocationActivity.this);
                request.addData("binLocation", LocationActivity.this.binLocation.getIdentifier());
                JSONObject binData = request.processRequest();

                try {
                    Log.i(TAG, "binData: " + binData.getString("STATUS"));
                } catch (JSONException e) {
                    Log.e(TAG, e.getMessage());
                }
            }
        });
        collectBin.start();

        this.binLocationValue.setText(this.binLocation.getIdentifier());
        this.partNumberValue.setText("Bogus Part Number");
        this.partDescValue.setText("Bogus Part Description");
        this.qtyValue.setText("None");
    }
}

Here is my OkHttpClient wrapper class:

package com.doublel.keystone.model;

import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import android.widget.Toast;

import com.doublel.keystone.R;

import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class JSONHTTPRequest {
    public static final String TAG = JSONHTTPRequest.class.getSimpleName();
    public static final String REQUEST_TYPE_GET = "GET";
    public static final String REQUEST_TYPE_POST = "POST";

    protected String url;
    protected String requestType;
    protected Activity caller;
    protected Map<String, String> data;
    protected JSONObject response;

    public JSONHTTPRequest(String url, String requestType, Activity caller) {
        this.url = url;
        this.requestType = requestType;
        this.caller = caller;
        this.data = new HashMap<>();
    }
    public String getUrl(){
        return this.url;
    }
    public void setUrl(String url){
        this.url = url;
    }
    public void addData(String key, String value){
        this.data.put(key, value);
    }
    public String getData(String key){
        return this.data.get(key);
    }
    protected boolean isNetworkAvailable() {
        ConnectivityManager manager = (ConnectivityManager) this.caller.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();

        boolean isAvailable = false;
        if(networkInfo != null && networkInfo.isConnected()){
            isAvailable = true;
        }
        return isAvailable;
    }
    public JSONObject processRequest()
    {
        if(isNetworkAvailable()) {
            OkHttpClient client = new OkHttpClient();
            Request.Builder builder = new Request.Builder();
            switch(this.requestType) {
                case REQUEST_TYPE_GET:
                    String queryString = this.generateQueryString();
                    Log.i(TAG, queryString);
                    builder.url(this.url + queryString);
                    break;
                case REQUEST_TYPE_POST:
                    builder.url(this.url);
                    break;
            }
            Request request = builder.build();
            Call call = client.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    Log.e(TAG, "Response failed with error: ", e);
                    e.printStackTrace();
                }

                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                    try {
                        if (response.isSuccessful()) {
                            String jsonData = response.body().string();
                            Log.i(TAG, jsonData);
                            JSONHTTPRequest.this.response = new JSONObject(jsonData);
                        } else {
                            JSONHTTPRequest.this.response = new JSONObject();
                        }
                    } catch (IOException e) {
                        Log.e(TAG, "IO Exception caught: ", e);
                    } catch (JSONException e) {
                        Log.e(TAG, "JSON Exception caught: ", e);
                    }
                }
            });
        } else {
            Toast.makeText(this.caller.getApplicationContext(), this.caller.getString(R.string.network_unavailable_message), Toast.LENGTH_LONG).show();
        }
        return this.response;
    }

    private String generateQueryString() {
        String queryString = "?";
        Iterator i = this.data.entrySet().iterator();
        while(i.hasNext()){
            Map.Entry pair = (Map.Entry) i.next();
            queryString += pair.getKey() + "=" + pair.getValue() + ",";
        }
        return queryString.substring(0, queryString.length() - 1);
    }
}

How can I get the wrapper class and the Activity to run on the same thread to avoid the errors I'm getting?

1 Answer

In case anyone is interested, I found the answer. OkHttp has a synchronous get option. This needs to be used in the wrapper class (in my case, JSONHTTPRequest). Since network requests need to run on a separate thread, that separate thread will be run from the Activity class. The code snippets of my wrapper and Activity classes are shown below:

Activity class:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_location);

        Thread collectBin = new Thread(new Runnable() {
            public void run() {
                try {
                    JSONHTTPRequest request = new JSONHTTPRequest("http://test.ll.com/api/getBinLocationInformation.php", JSONHTTPRequest.REQUEST_TYPE_GET, LocationActivity.this);
                    request.addData("binLocation", LocationActivity.this.binLocation.getIdentifier());
                    JSONObject binData = request.processRequest();
                    JSONArray partsList = binData.getJSONArray("VALUES");
                } catch(JSONException e){
                    Log.e(LocationActivity.TAG, e.getMessage());
                }
            }
        });
        collectBin.start();
    } 

JSONHTTPRequest class

package com.doublel.keystone.model;

import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import android.widget.Toast;

import com.doublel.keystone.R;

import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class JSONHTTPRequest {
    public static final String TAG = JSONHTTPRequest.class.getSimpleName();
    public static final String REQUEST_TYPE_GET = "GET";
    public static final String REQUEST_TYPE_POST = "POST";

    protected String url;
    protected String requestType;
    protected Activity caller;
    protected Map<String, String> data;
    protected JSONObject response;

    public JSONHTTPRequest(String url, String requestType, Activity caller) {
        this.url = url;
        this.requestType = requestType;
        this.caller = caller;
        this.data = new HashMap<>();
        this.response = new JSONObject();
    }
    public String getUrl(){
        return this.url;
    }
    public void setUrl(String url){
        this.url = url;
    }
    public void addData(String key, String value){
        this.data.put(key, value);
    }
    public String getData(String key){
        return this.data.get(key);
    }
    protected boolean isNetworkAvailable() {
        ConnectivityManager manager = (ConnectivityManager) this.caller.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = manager.getActiveNetworkInfo();

        boolean isAvailable = false;
        if(networkInfo != null && networkInfo.isConnected()){
            isAvailable = true;
        }
        return isAvailable;
    }
    public JSONObject processRequest()
    {
        if(isNetworkAvailable()) {
            OkHttpClient client = new OkHttpClient();

            Request.Builder builder = new Request.Builder();
            switch(this.requestType) {
                case REQUEST_TYPE_GET:
                    String queryString = this.generateQueryString();
                    builder.url(this.url + queryString);
                    break;
                case REQUEST_TYPE_POST:
                    builder.url(this.url);
                    break;
            }
            Request request = builder.build();

            try (Response response = client.newCall(request).execute()) {
                if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
                this.response = new JSONObject(response.body().string());
            } catch(IOException e) {
                Log.e(TAG, e.getMessage());
            } catch(JSONException e){
                Log.e(TAG, e.getMessage());
            }
        } else {
            Toast.makeText(this.caller.getApplicationContext(), this.caller.getString(R.string.network_unavailable_message), Toast.LENGTH_LONG).show();
        }
        return this.response;
    }

    private String generateQueryString() {
        String queryString = "?";
        Iterator i = this.data.entrySet().iterator();
        while(i.hasNext()){
            Map.Entry pair = (Map.Entry) i.next();
            queryString += pair.getKey() + "=" + pair.getValue() + ",";
        }
        return queryString.substring(0, queryString.length() - 1);
    }
}