OkHttp, the Android HTTP Client from Square
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 RequestBody
s.
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 RequestBody
s 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 AsyncTask
s, 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.