Build a Simple Networking Library in Android (Part 1)
The idea of library internals has always been intimidating to me. Most people being an app developer prefer to assume the library as a black box. How do people code it, test it or release it? You are in for a treat as in my series of articles I will take you on a journey of creating, running and publishing an actual working library. Yes, we won’t be doing some “Hello World” stuff. We will create a real Android Library through which can make API requests and get the result. It will be 2 part series:
- All about the creation of the networking module
- Locally publish our library and run in demo/test app, check here
A little sneak peek to get you excited!
We will try to accomplish a very Simple Networking Library. It will make real network calls from API and give a response in JSONObject. Our library will model a network request by taking a URL, Method, Header, Body and return response via an interface.
Want to have a sneak peek of a simple GET request on a mock API? Below is a code snippet that the library will expose:
Creation of networking module
To begin with, we will create a new module that will contain all the library internals. Our module will comprise all the code necessary for making the network request, parsing the response, thread management and last but not least, returning response via an interface. Let’s head over to the starting point!
The Starting point
In the current Android Studio, it’s straightforward to get started for library development. Just a few clicks, no manual editing in any file.
Go to: File > New > New Module > Android Library. You will get something like this:
Create New Module has five options:
- Module name: name of your library (I have named it simple-networking)
- Package name: unique name to differentiate with other libraries
- Language: We are going to focus on Kotlin
- Bytecode Level: 8 means Java 8 (if you want Java 7 you change from the drop-down)
- Minimum SDK: Minimum Android API level for this SDK to compile.
Caution: The project which will be using our library it’s API level ≥ Libray API level. So choose the version carefully. For simplicity, I have kept the API level as 21 (94% device support)
Now hit Finish, the module is ready (after Gradle sync and indexing). Now we can talk about the architecture of the library module.
The Architecture
The libraries lack fixed structure defined as compared to app development. It depends on what you want to build and then have an architecture. We are building a Simple Networking library. It is supposed to make a network request, then parse the raw response and give a useful result back via an interface. For someone who is integrating our library should be able to configure fields like URL, HTTP Methods, Message Body, Request Headers. Here are the requirements for what we are building:
- Request Builder: A Builder class to set URL, method, body, header. It also has a ThreadExecutor to execute the request on bg thread. And lastly, a JSONObjectListener to listen for API response in JSON.
- RequestTask: Takes the Request Builder as a parameter to make the connection request and parse the response. It will give the acknowledgement back in a Response class as JSONObject.
We will try to put Request, RequestTask & Response in a single class named Http. Now that we have decided the necessary things for architecture. Let’s code the above two, starting with Request Builder.
1. Request Builder
This class will be responsible for creating the network Request. It will be a Builder class which will take the HTTP method in constructor. Supported methods will be GET, POST, DELETE, PUT. The other configurable parameters will be URL, Request Header and Request Body
Other configurable parameters will be URL, Request Header and Message Body:
- URL: It stands for Uniform Resource Locator. A URL is nothing more than the address of a given unique resource on the Web. The builder for URL will look like this:
The
var url
isinternal
as we already exposed the function get the URL. Firstly we will assign the receivedurl
to our class member then we will returnthis
to follow Builder pattern. Check here, for learning about Builder pattern.
2. Request Header: According to Mozilla, HTTP headers let the client and the server pass additional information with an HTTP request or response. You can read more about headers here. By default, there are always some headers, but we can enable the functionality to add custom headers (if any). The builder for the header will look this:
The
var header
is internal as we already exposed the method get the header. We have added support to only allow header as a map (key-value pair). So we need to check whether it has any key-value pair, i.e. by isNullOrEmpty()
. And then if all good we will put all the data from the received map in the class member’s map
3. Message Body: The message body part is optional for an HTTP message. It carries the entity-body associated with the request or response. You can read more about headers here. The builder for the body looks like this:
The
var body
is internal as we already exposed the method get the body. We have kept body asByteArray
as theHttpURLConnection
supports onlyByteArray
as a type for body. But to make it easier for integration, we have to expose thebody()
function withJSONObject
. Internally we are converting theJSON
toString
and then theString
to abyte array
. And then store it in the class member namedbody
. Also, we have addedContent-Type
asapplication/json
in the header as our body type isJSON
. It is necessary as TheMIME
media type forJSON
text isapplication/json
We have two more methods to support our Request class to make the connection request and send the response back. Before this, we need two more things, a ThreadExecutor (for creating a background thread) and JSONObjectListener (interface to give back the API response).
ThreadExecutor: Creates and executes thread for the connection request on the background thread.
execute()
method takes arunnable
and creates aHandlerThread
by assigning the priority. To make it easier, we have usedProcess.THREAD_PRIORITY_DEFAULT
, read more about it here. After making the thread (viahandlerThread
) we will create thehandler
object. Lastly, post therunnable
via thehandler
.
Why not coroutines in place of HandlerThread?
We will have to integrate the coroutine library. Then our library will be bloated, which is not a good practice.
JSONObjectListener: This interface will give a response or failure from the API.
The response will be
org.json.JSONObject
and failure will bejava.lang.Exception
Now we can bring back our focus on two more remaining methods to support our Request
class, to make the connection request and send the response back:
- makeRequest(): makes the connection request for the existing builder, the
Request
object.
jsonObjReqListener
is assigned to a class member here. This method takes an interface as an argument which will give the response back to the user.ThreadExecutor
is used to pass this (currentRequest
) inRequestTask
which is aRunnable
class as theThreadExecutor’s execute()
will take onlyRunnable
(can be checked above).RequestTask
, discussed in detail below.
2. sendResponse(): An internal method to invoke the response interface.
The above also takes a
Response
object (used to store theJSON
response), theResponse
class is in detail below. It also takes care of failed requests. If theException(e)
is not null then it is an exception else a success response comprisingJSONObject
.
Now we are done with the Request
part. It contains everything required to make an API request. Check here to get the full overview of Request
class. Now we will focus on how we can use the Request
inside RequestTask
.
2. Request Task
This class takes the above class i.e. Request
as a constructor parameter. This class implements Runnable
class so the main thread is not blocked. This class is Runnable
as our current ThreadExecutor
takes only Runnable
class as a parameter. The signature of our class is as follows:
internal class RequestTask(private val req: Request) : Runnable {}
It is kept internal as we don’t want to expose it as it is not needed for someone who is integrating our library. RequestTask
has three methods:
- run(): as the class implements runnable so this method will be overridden.
The run method creates a object of HttpURLConnection
by calling getHttpURLConn()
. The connection is then passed to parseResponse()
. The req
is actually Request
class obj (passed in the constructor) which is used to access the sendResponse()
method, in turn give the response back. The above also handles the exception scenario and sends back the apt response.
2. request(): Creates an object of HttpURLConnection
. This method is the most core part of our library as it makes the connection writes the response as a stream.
Creates the obj by taking the
req
i.e.Request
(passed in the constructor). It takes theURL
and then opens the connection. Then takes the method (i.e. GET, POST, PUT, DELETE). Also iterates through the request header map, and writes the request body in the connection stream. In a nutshell, whatever we have used in the Builder classRequest
will be used here to make the connection. Lastly, makes the connection by callingconnect()
.
3. parseResponse(): Parses the response received from the network call. Basically, the network gives back the response in ByteArrayOutputStream
. The below method converts it into a useful/readable Response
.
The above method checks for the valid status code. We are considering
2xx
as success. After this, we get theInputStream
, also taking care of the invalid status by reading the error response. Now theInputStream
can be written toByteArray
and gives it toResponse
class (discussed below). It also disconnects after done with the request. Take a look at the fullRequestTask
here.
Response class: It converts the ByteArray
response to JSONObject
and stores it.
The
ByteArray
response gets converted to aString
by encoding it asUTF-8
. And then aJSONObject
is created from theString
. We can argue that we could have parsed directly toJSONObject
.Response
as a separate class allows us to extend functionality (in future maybeJSONArray
) & keep things separated.
3. Assembling Request, RequestTask and Response
If you remember the sneak peek into a code I had mentioned in a section above. I had some Http.Request
something? That’s why we will now assemble Request
, RequestTask
& Response
in a single class. I have named it Http.kt
, take a look at the same here. It’s a fairly large class so it’s not posted below and you can easily check on Github gist. I have kept it as object class because Kotlin
provides an easy way to create singletons using the object declaration feature.
We are done with the core part of our library module (simple-networking). But at this stage, this library can’t be used in any project. In order to get there, we need to publish our library. Check out part 2 to learn this.