OkHttp, the Android HTTP Client from Square

Valdio Veliu
Share

Most apps need network connections to external services to access and exchange data. This is typically through REST APIs and an HTTP client in your app. OKHttp is an Android HTTP client library from Square that reduces the steps needed, and means you can spend more time on the important areas of your app.

Why OkHttp?

It’s a simple library to use, loading data faster and saving bandwidth. It removes the need for network testing, recovering from common connection problems, and on a connection failure, OkHttp can retry the request with a different route.

OkHttp supports Android 2.3 and above, which is more than 99% of the market based on current statistics of version usage.

Using OkHttp in Your App

OkHttp recently updated to version 3.0, and is currently at version 3.3.1. I mention this because OkHttp made key updates in this version and methods from previous versions are deprecated.

You can find all the examples I will cover on GitHub.

Add Dependencies

Open build.gradle(Module: app) and add the following dependency, or check the OkHttp site for the latest updates.

dependencies {
    //...
    compile 'com.squareup.okhttp3:okhttp:3.3.1'
}

In AndroidManifest.xml add the internet permission.

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

Networking with OkHttp

The main focus of this article consists will be on building the following network calls.

  • GET
  • POST
  • Uploading files with OkHttp
  • Monitoring request and response progress
  • Handle cookies with OkHttp

Note: I try not to write redundant code, but DRY code. I feel I’ve done my best to optimize how my code handles networking with OkHttp. This consist of structuring code into two classes. ApiCall to handle POST and GET calls and RequestBuilder to construct the request URLs and RequestBodys.

Networking

Time to create some API calls, specifically the GET and POST network requests. Create the following java class to handle each of the requests.

public class ApiCall {

    //GET network request
    public static String GET(OkHttpClient client, HttpUrl url) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .build();
        Response response = client.newCall(request).execute();
        return response.body().string();
    }

    //POST network request
    public static String POST(OkHttpClient client, HttpUrl url, RequestBody body) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        Response response = client.newCall(request).execute();
        return response.body().string();
    }
}

In both the methods of the ApiCall class there is an instance of OkHttpClient, which means you must pass an instance of the client on every network call. You can construct the methods differently so that a new client is built each time, but this is not a smart solution and I recommend you read this StackOverflow answer, to prove this is a better solution.

In both the methods the HttpUrl url parameter represents the URL of the request. It’s represented as an instance of HttpUrl, but can also be a String:

public static String POST(OkHttpClient client, String url, RequestBody body) throws IOException {
    //...
}

You can apply this in both methods and it has the same result as the HttpUrl instance. An important fact to mention is that the URL String might be incorrectly structured which can lead to a MalformedURLException.

The last thing to mention is the RequestBody in the POST method. It represent a collection of parameters, in name/value pairs sent in the body of a POST request.

Building the Requests URL

Next you need to create a Java class that constructs the URL of a network request and the RequestBodys of the POST methods.

public class RequestBuilder {

    //Login request body
    public static RequestBody LoginBody(String username, String password, String token) {
        return new FormBody.Builder()
                .add("action", "login")
                .add("format", "json")
                .add("username", username)
                .add("password", password)
                .add("logintoken", token)
                .build();
    }

    public static HttpUrl buildURL() {
        return new HttpUrl.Builder()
                .scheme("https") //http
                .host("www.somehostname.com")
                .addPathSegment("pathSegment")//adds "/pathSegment" at the end of hostname
                .addQueryParameter("param1", "value1") //add query parameters to the URL
                .addEncodedQueryParameter("encodedName", "encodedValue")//add encoded query parameters to the URL
                .build();
        /**
         * The return URL:
         *  https://www.somehostname.com/pathSegment?param1=value1&encodedName=encodedValue
         */
    }

}

The RequestBuilder java class contains examples for building a RequestBody for POST calls and HttpUrl instances. The examples consist of building a RequestBody for some presumed login action and an HttpUrl, usually used on a GET requests.

Back to Activity

The steps so far are to prepare the way to handle networking from an activity and make use of the classes created. First, in an activity class create a global OkHttpClient.

public class MainActivity extends AppCompatActivity {

    private OkHttpClient client;
    //...
}

Initialize the client in the onCreate() method.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //...
        client = new OkHttpClient();
    //...
}

You must handle network calls in Android asynchronously, otherwise a NetworkOnMainThreadException is thrown when an application attempts to perform a networking operation on the main thread. To avoid this exception, use AsyncTask.

Add these methods to your Activity:

private void loadContent() {
    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            try {
                response = ApiCall.GET(client, RequestBuilder.buildURL());
                //Parse the response string here
                Log.d("Response", response);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }.execute();
}

private void attemptLogin(String url) {
    new AsyncTask<String, Void, Void>() {
        @Override
        protected Void doInBackground(String... params) {

            try {
                response = ApiCall.POST(
                        client,
                        params[0],
                        RequestBuilder.LoginBody("username", "password","token"));

                Log.d("Response", response);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }.execute(url);
}

The two functions shown here represent both examples in the RequestBuilder class. They are just for demonstrative purposes and you could construct them differently.

Leaving aside the implementation of the AsyncTasks, notice the code that handles networking. All the code implemented in both previous classes is structured in a single line, which return the response body of the network request and there is no redundant code. Pretty cool!

ApiCall.GET(client, RequestBuilder.buildURL());

After you have retrieved the response, some parsing might be needed, as APIs commonly return JSON or XML feeds.

Uploading Files

In this section I will cover uploading files with OkHttp. One of the common upload types required is multipart/form-data . The following image upload example is how OkHttp could handle this, but is valid for other different file types.

//Upload request body
public static MultipartBody uploadRequestBody(String title, String imageFormat, String token, File file) {

    MediaType MEDIA_TYPE = MediaType.parse("image/" + imageFormat); // e.g. "image/png"
    return new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("action", "upload")
            .addFormDataPart("format", "json")
            .addFormDataPart("filename", title + "." + imageFormat) //e.g. title.png --> imageFormat = png
            .addFormDataPart("file", "...", RequestBody.create(MEDIA_TYPE, file))
            .addFormDataPart("token", token)
            .build();
}

This function returns a MultipartBody object derived from RequestBody, so it’s a good idea to put this method in the RequestBuilder class.

Now that the body of the upload request is complete, how do you run the upload call? Uploads are POST calls to the API, so its the same as the previous calls.

 File file = new File(""); //provide a valid file
 ApiCall.POST(client, url, RequestBuilder.uploadRequestBody("title", "png", "someUploadToken", file));

Remember to call this asynchronously. This call returns the verification of the upload action, successful or not.

What’s Next

That’s all for the basics of OkHttp, but next I will mention some tips and tools that might be useful with OkHttp.

Monitoring Request Progress

Often useful when uploading media is the ability to monitor progress. Some time ago I found this Gist solution that is the solution to this problem. To monitor a request’s progress with this solution, you create an instance of RequestBody and then pass it as an argument in the constructor of the class.

//Monitored request
File file = new File("");
MultipartBody body = RequestBuilder.uploadRequestBody("title", "png", "someUploadToken", file);

CountingRequestBody monitoredRequest = new CountingRequestBody(body, new CountingRequestBody.Listener() {
    @Override
    public void onRequestProgress(long bytesWritten, long contentLength) {
        //Update a progress bar with the following percentage
        float percentage = 100f * bytesWritten / contentLength;
        if (percentage >= 0) {
            //TODO: Progress bar
        } else {
            //Something went wrong
        }
    }
});

I changed the code for use it with OkHttp 3. Here is a link to the class in GitHub with the appropriate changes .

Handle Cookies with OkHttp

You can handle cookies in OkHttp with CookieJar. The following example is an example of how to handle cookies in OkHttp.

public class CookieStore implements CookieJar {

    private final Set<Cookie> cookieStore = new HashSet<>();

    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        /**
         *Saves cookies from HTTP response
         * If the response includes a trailer this method is called second time
         */
        //Save cookies to the store
        cookieStore.addAll(cookies);
    }

    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        /**
         * Load cookies from the jar for an HTTP request.
         * This method returns cookies that have not yet expired
         */
        List<Cookie> validCookies = new ArrayList<>();
        for (Cookie cookie : cookieStore) {
            LogCookie(cookie);
            if (cookie.expiresAt() < System.currentTimeMillis()) {
                // invalid cookies
            } else {
                validCookies.add(cookie);
            }
        }
        return validCookies;
    }

    //Print the values of cookies - Useful for testing
    private void LogCookie(Cookie cookie) {
        System.out.println("String: " + cookie.toString());
        System.out.println("Expires: " + cookie.expiresAt());
        System.out.println("Hash: " + cookie.hashCode());
        System.out.println("Path: " + cookie.path());
        System.out.println("Domain: " + cookie.domain());
        System.out.println("Name: " + cookie.name());
        System.out.println("Value: " + cookie.value());
    }
}

To handle cookies based on the specified policies in the CookieStore class, you have to apply these policies to the OkHttpClient as follows.

OkHttpClient client = new OkHttpClient.Builder()
    .cookieJar(new CookieStore())
    .build();

Now you have updated the OkHttpClient to accept cookies, all the network requests cookies will be handled with the policies implemented in the CookieStore class.

This example is enough for session cookies, but for those who want to save cookies through subsequent app launches, this policies is not enough. There is a solution to this problem by using the PersistentCookieJar implementation based on SharedPreferences. To use this package with a OkHttpClient follow their usage guidelines.

All OK?

In this article I covered getting started with OkHttp and shared advice that might come in handy while working with it. I hope you find the library saves you time and would love to know what you create with it.

CSS Master, 3rd Edition