Reading string and JSON over network
Networking is an essential component of apps. Most of the apps we use are connected to the internet and involve reading/writing data over the internet. In this tutorial, we will learn how to perform network requests in Kotlin. Although you can also use a third-party library such as Retrofit, Volley and such, understanding how it's done in Kotlin is worthwhile. So let's get started!
Making a network request in Kotlin is straightforward with simple syntax. Here's how you would read data over the internet in Kotlin:
val response = URL("https://httpbin.org/get").readText()
However, of course, if you try it on the main thread, you will get the NetworkOnMainThreadException
exception. To get away with this, we need to make the network call in the background. One way to do this is by using an Async
task. An Async
task was a pain to implement in Java, but we can do it quite easily using Anko
(a library for Kotlin). This is how you would create a background task in Kotlin using Anko
:
doAsync{ val result = URL("https://httpbin.org/get").readText() uiThread { toast(result) } }
The uiThread
method is provided by Anko
library, which you can include in your project by adding these lines in your build.gradle:
implementation "org.jetbrains.anko:anko:0.10.4"
In Java's Async
task implementation, the Async
task could be fired even if the activity was being destroyed. This resulted in defensive programming where you had to make checks on whether the UI was still present to do UI operations. However, Anko's implementation of background task takes care of it, and it won't fire the task if the activity is dying.
The doAsync
returns a Java Future
. In simple words, a Future
is a proxy or a wrapper around an object that is not yet there. When the asynchronous operation is done, you can extract it. If you want to avoid working with Future
, doAsync
has a different construct that accepts an ExecutorService
:
val executor = Executors.newScheduledThreadPool(5) doAsync(executorService = executor){ val result = URL("https://httpbin.org/get").readText() uiThread { toast(result) } }
The uiThread
block isn't executed if the activity is closing. The reason is that it doesn't hold a context instance, only a weak reference. So even if the block isn't finished, the context will not leak.
We are going to update the AndroidManifest.xml file by adding the following user permissions
<uses-permission android:name="android.permission.INTERNET" />
How to download a file in Kotlin
We often need to download files in our Android application. The most basic way of doing this will be opening a URL connection and using InputStream
to read the content of the file and storing it in a local file using FileOutputStream
; all this is in a background thread using AsyncTask
. However, we don’t want to reinvent the wheel. There are a lot of libraries out there that handle all this stuff very nicely for us and make our work super easy, helping us create clean code.
For this recipe, we will be using a networking library called Fuel
, which is written in Kotlin. Add Fuel
dependencies to your project dependencies by adding the following lines in your build.gradle and syncing the project:
compile 'com.github.kittinunf.fuel:fuel:1.12.1'
Let's start with a button in our view with an onClickListener
attached to it. I am also adding a progressBar
to the view to be able to see the progress of our download. This is my view:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <Button android:id="@+id/btnDownload" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="32dp" android:layout_marginTop="32dp" android:text="Download" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:progress="0"/> </android.support.constraint.ConstraintLayout>
Let's start by downloading a temporary file. We will be using https://httpbin.org/ for mocking download file API. The following is the code for downloading a temporary file:
class DownloadFileActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_download_file) btnDownload.onClick { progressBar.progress = 0 Fuel.download("http://httpbin.org/bytes/32768").destination { response, url -> File.createTempFile("boo", ".tmp") }.progress { readBytes, totalBytes -> val progress = readBytes.toFloat() / totalBytes.toFloat() progressBar.progress = progress.toInt()*100 }.response { req, res, result -> Log.d("status result", result.component1().toString()) Log.d("status res", res.responseMessage) Log.d("status req", req.url.toString()) } } } }