Overview of Android Networking Tools: Receiving, Sending, Inspecting, and Mock Servers
When the applications are complex, with many requests to the network, various tools and libraries can help you to work with the network and search for problems.
Join the DZone community and get the full member experience.
Join For FreeToday all applications are connected to the Internet in one way or another. When developing simple applications, as a rule, there are no problems. Another thing is when the applications are complex, with many requests to the network. In this case, various tools and libraries can help you to work with the network and search for problems. We will study them in this article. I hope you will find something new, although some things seem familiar to you.
Remarks and Assumptions
First, I want to draw your attention to some remarks and assumptions:
- In this article, we will look at specific examples whose code can be inserted into Android Studio, run and see what happens;
- This article will focus on working with the Internet via HTTP/HTTPS. We will not consider WebSocket and streaming protocols;
- Also, we will not consider libraries for specific types of data, for example, to download pictures like glide and picasso;
- We will go from simple things gradually complicating them;
- The code will not be broken into layers according to MVP and MVVM patterns, etc. This is done to simplify the examples and the article as a whole;
- Do not forget to put the permission in the manifest <uses-permission android:name=”android.permission.INTERNET”/>;
- This article is meant for mid-level developers and above so that we won’t dwell on very simple things; for the same reason, we won’t go into every single example or library. Only an overview of the tools is given to broadening your horizons;
Ways to Receive and Send Data on Android
Selecting an Example
For example, we will use the Google account on GitHub because the API is open and does not require additional token requests and other credentials.
Let’s go to the page. This is what it looks like:
Note the description with the heart: “Google ❤️ Open Source.” Now let’s turn to the API of this page.
It looks like this:
Pay attention to the heart in the description: it is present, everything is normal, and the data corresponds.
We have chosen to work with an open API. Now we use it to test the tools that will be discussed below.
HttpUrlConnection
Let’s get this string on the android device. First, let’s take the native HttpUrlConnection tool and try to use it.
We don’t need to add any libraries.
Create an Activity and put the following code in it:
class HttpUrlConnectionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread { request() }.start()
}
private fun request() {
val url = URL("https://api.github.com/orgs/google")
val connection = url.openConnection() as HttpURLConnection
val response = connection.inputStream.bufferedReader().use(BufferedReader::readText)
Log.d("HttpExample", "response: $response")
}
}
Run the application and get to LogCat:
D: response: {“login”:”google”,”id”:1342004,”node_id”:”MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=”,”url”:”https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source”,”name”:”Google”,”company”:null,”blog”:”https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
In the log, we got the same line as in the browser. Note the line with the heart. As you can see, it only took 5–10 lines of code to get a response.
This example doesn’t handle errors and responses from the server. We are betting that everything is working fine.
Pros and Cons:
- + This native tool is always available for use;
- + no need to pull additional libraries;
- + Android is present from day one, so everything is tested as much as possible, and all bugs are fixed;
- - difficult to use for catching exceptions;
- - difficult to use for building complex queries and passing headers. Here we are actually working with strings.
This tool lies under the hood of some libraries, including cross-platform, so to understand the essence of their work, you need to know it.
Now let’s move on to libraries that essentially do the same thing as the native HttpUrlConnection tool but make the developer’s life much easier.
Okhttp
Let’s start with Square’s Okhttp library.
Let’s add an import:
implementation ‘com.squareup.okhttp3:okhttp:4.0.1’
Create an Activity and insert the following code:
class OkhttpActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread { request() }.start()
}
private fun request() {
val request = Request.Builder().url("https://api.github.com/orgs/google").build()
val response = OkHttpClient().newCall(request).execute().body?.string()
Log.d("HttpExample", "response: $response")
}
}
We get the same response with a heart as in the last version with HttpUrlConnection:
D: response: {"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","url":"https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source","name":"Google","company":null,"blog":"https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
As you can see, the answer was again given in a few lines. Visually it is not much different from the last version. But this is in such a simple case. The differences will be noticeable in the more complex case. This library has simplified the way headers are passed. You can read more here.
Pros and Cons:
- + The method of transferring headers and body has been simplified;
- - additional library slightly increases the size of the application.
Retrofit
Let’s take a look at Retrofit, also from Square. This library does essentially the same thing as Okhttp. But there are significant optimizations when you need to make complex URL queries.
We add dependencies:
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
Note that in addition to the Retrofit library, RxJava and its adapters are added. This is because Retrofit does not give a string, but a RxJava object, such as Single. This makes it easier to work with multi-threading. Retrofit can also give LiveData and Flow.
Making an Activity with the following code:
class RetrofitActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_retrofit)
findViewById<Button>(R.id.buttonActivityRetrofit).setOnClickListener {
request()
}
}
private fun request() {
Retrofit.Builder()
.baseUrl("https://api.github.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build()
.create(RetrofitService::class.java)
.user
.subscribeOn(Schedulers.io())
.subscribe { response ->
Log.d("HttpExample", "response: $response")
}
}
interface RetrofitService {
@get:GET("/orgs/google")
val user: Single<String>
}
}
We get the answer:
D: response: {"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","url":"https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source","name":"Google","company":null,"blog":"https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
As you can see, Retrofit doesn’t insert the whole URL. It’s broken up. Part of the path is moved to the RetrofitService interface. This both complicates development in terms of the amount of code and simplifies it when making complex URL queries. Otherwise, the result is the same.
Pros and Cons:
- + Can convert to Gson, you need to; add .addConverterFactory(GsonConverterFactory.create(gson));
- + gives away RxJava, liveData, and Flow objects. To do this, you need to add, for example, RxJava2CallAdapterFactory.create();
- + it’s easier to create complex URLs, and pass query parameters and headers there;
- - more complicated structure and more code.
We’ve covered the most popular methods of working with the network on Android. Next, we’ll look at the next stage of the data path from the mobile device to the server — tools and libraries for code inspection.
Ways to Inspect Requests and Responses From the Server
Android Studio Network Inspector
There are times when there seem to be no problems in the code, the server also works, but the desired result is not achieved. Or errors come from the server. Perhaps the problem is in the header or the wrong URL link. To detect this, you need to use tools that can inspect traffic—for example, Android Studio Network Inspector.
This is the easiest tool to inspect. It does not require any additional libraries and settings. The algorithm is as follows:
- Go to the App Inspection tab in Android Studio. In previous versions of Android Studio, Network Inspector was located in the Profiler tab;
- Establish a connection with your device;
- On your device, make a request to the server, for example, one of those mentioned above;
- Select the curve that appears with the mouse;
- Go to the Request tab to see the request parameters;
- Go to the Response tab to see the headers and incoming data.
The screenshot below shows that we see the same response from Github as before:
Pros and Cons:
- + No need for additional libraries and settings;
- - you can’t log requests and responses.
Next, consider the HttpLoggingInterceptor library, which is needed when you want to not only view requests and responses but also record and store them.
HttpLoggingInterceptor
This is Square’s most popular inspection library.
Adding a library:
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.1'
Create an Activity with the following code:
class HttpLoggingInterceptorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
request()
}
private fun request() {
val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.i("HttpExample", "message: $message")
}
})
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
Retrofit.Builder()
.baseUrl("http://api.github.com")
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build()
.create(RetrofitService::class.java)
.user
.subscribeOn(Schedulers.io())
.subscribe { response ->
Log.d("HttpExample", "response: $response")
}
}
interface RetrofitService {
@get:GET("/orgs/google")
val user: Single<String>
}
}
See the answer in LogCat:
I: message: {"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","url":"https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source","name":"Google","company":null,"blog":"https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
D: response: {"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","url":"https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source","name":"Google","company":null,"blog":"https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
We got 2 identical logs. This may seem pointless in this simple example. However, when you have hundreds of requests scattered over a thousand different files, it makes sense to log the requests and responses in a single location in the application.
Pros and Cons:
- + It is possible to accumulate logs;
- + you can edit headers;
- - increases the size of the application, like any library.
So, with the help of the HttpLoggingInterceptor library, you can not only view requests and responses, but also output them to LogCat and, if necessary, write them to a file or send them to a server. Next, a few words about the previously popular Stetho tool.
Stetho
Stetho was actively used for inspections before Android Studio Network Inspector appeared. This is a library from Facebook. It is essentially a mix of Android Studio Network Inspector and HttpLoggingInterceptor. It can output logs to the Google Chrome browser and logging. We will not dwell on it in detail because it is becoming unsupported. The latest versions of the browser do not load the information (see the screenshot below), but logging is still supported. Maybe you can get some useful features from it. Read more about this library here.
Wireshark
So far, we have considered internal tools and libraries, i.e., which can be applied when the source code of an application is available. However, sometimes there are situations when there is no access to the source code. The Wireshark tool will help you to solve this problem.
We will consider an example with traffic without encryption, i.e., HTTP and not HTTPS. Below I will explain why.
Download and install Wireshark.
Next, go to AndroidManifest and add a line:
<application
...
android:usesCleartextTraffic="true"
...
</application>
Change the address to http://api.github.com/orgs/google (HTTPS to HTTP):
class WiresharkActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread { request() }.start()
}
private fun request() {
val request = Request.Builder().url("http://api.github.com/orgs/google").build()
val response = OkHttpClient().newCall(request).execute().body?.string()
Log.d("HttpExample", "response: $response")
}
}
Open the program Wireshark. Find your network and double-click on it:
In the search box, we enter the filter word HTTP. We are left with only two lines:
Next, run the application on an emulator(!). It won’t work on a real device.
We see that a request was sent to api.github.com. However, no response was received because 301 Moved Permanently, because the traffic was redirected to https://api.github.com, and the response was already received from it:
In Wireshark, you can see all the data and headers that have been sent and received. The same can be done with HTTPS. To do this, you need to decrypt the traffic.
Pros and Cons:
- + You can view traffic down to a low level, down to the bits;
- + you don’t have to have access to the source code of the application;
- - difficult to work with the secure HTTPS protocol in order to decrypt;
- - need to run on the emulator because it captures traffic from the computer, not the mobile device;
- - quite complicated to use.
This concludes our look at inspection tools and moves on to a way to create a mock server if there is no real one to test the application.
Ways to Create a Mock Server
Postman
Sometimes it is necessary to test the communication with the server when the backend is not yet ready or there is no access to the server from inside to change the parameters to find the error. In this case, you can create your own mock server and work with it.
Download Postman and run it. To do this, you will need to log in to your account.
Click on the Collections tab and create a new collection:
Go to the Mock Servers tab and create a new mock server by selecting the already created collection:
Next, you need to copy the URL of the created server:
Go back to the Collections tab and add a new query:
The following is for this example:
- Select the GET method.
- Insert the copied URL.
- Add a slash and some postfix; for example, I put /status.
- Choose the answer code OK 200.
- Choose the JSON response type.
- Insert any answer in JSON format, for example {“status”: “good”}.
Check that everything works. To do this:
- Go back to the Request tab.
- Insert the full URL into the request line and click “Send.”
- At the bottom of the page, look for the answer.
This server is created not only on the local computer but also available on the Internet. You can check it by pasting a string into your browser and seeing the answer:
Now it remains to get JSON on the android device. To do this, in the example above, OkhttpActivity insert the URL and restarts the application:
class PostmanActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread { request() }.start()
}
private fun request() {
val url = URL("https://7bb01ebf-d698-4c65-94c0-7625cf817d06.mock.pstmn.io/status")
val connection = url.openConnection() as HttpURLConnection
val response = connection.inputStream.bufferedReader().use(BufferedReader::readText)
Log.d("HttpExample", "response: $response")
}
}
We get the answer we expected to get:
D: response: {
"status": "good"
}
Pros and Cons:
- + You can use a mock server when the prod-server is not ready yet;
- + can be used when you need to change server-side parameters but don’t have access to its code;
- - it is an incomplete server, so it is not possible to fully simulate the work of the server. For example, you can not send photos and other bit data.
As you can see, Postman’s mock server feature can be very useful in many situations when developing an android application.
Conclusion
So today’s android developer requires that all applications support networking. As a rule, in simple situations, this is not difficult. However, the situation is quite different in applications with 1M+ lines of code and hundreds of different requests. In such cases, you have to use various tricks and non-standard methods to achieve the desired result. The libraries and tools I’ve offered above are meant to help you to solve some problems.
If you use any other tools or approaches not described above, please share them in the comments.
Opinions expressed by DZone contributors are their own.
Comments