What is Kotlin?

Kotlin is a statically typed programming language that runs on the Java virtual machine and also can be compiled to JavaScript source code or use the LLVM compiler infrastructure.

Kotlin can do anything Java can including creating android apps. And in fact in this class we create an android app that downloads JSON data when a button is clicked, parses that data and renders the result in a custom gridview.

Watch our video tutorial here:

What is JSON?

JSON stands for JavaScript Object Notation.
It is a lightweight data-interchange format.
It is not only easy for humans to read and write but also easy for machines to parse and generate. That’s why JSON is quite popular.

JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.

What is AsyncTask?

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

We use AsyncTask in this class instead of raw threads. We will be downloading data in the background thread. Also we parse data in the background thread. AsyncTask will allow us do these while reporting progress using Progress Dialog.

What is HttpURLConnection?

HttpURLConnection is a URLConnection with support for HTTP-specific features. Through this class we will open a connection to a URL pointing us to JSON data and download that data.

We are basically making a HTTP GET request.

What is a GridView?

A gridview is a view that shows items in two-dimensional scrolling grid. The items in the grid come from the ListAdapter associated with this view.

In this class we want to see how to make a HTTP GET request in Kotlin Android using the standard HttpURLConnection class, download some JSON data, parse that data and bind to a custom gridview.

We use AsyncTask for threading, JSONObject and JSONArray for threading.

Here’s the JSON sample we are using: https://jsonplaceholder.typicode.com/users.

Let’s go.

Project Structure

Here’s our project structure:

AndroidManifest.xml

Let’s add our internet connectivity permission in our androidmanifest. This is needed because we will be connecting to internet to download JSON data.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.camposha.kotlin_json">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

row_model.xml

This is our row or grid model so to speak. This layout will get inflated into a single grid item in our GridView.

We are working with a custom gridview so that’s why create a custom layout. This layout will contain an ImageView and several textviews.

The ImageView will display a static image while the TextViews will be populated with data parsed from json.

Take note that at the root of our XML we have a CardView with custom cardCornerRadius and cardElevation.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_margin="5dp"
    card_view:cardCornerRadius="5dp"
    card_view:cardElevation="10dp"
    android:layout_height="300dp">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="150dp"
            android:padding="10dp"
            card_view:srcCompat="@android:color/holo_red_light" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:text="Name"
                android:id="@+id/nameTxt"
                android:padding="5dp"
                android:textColor="@color/colorAccent"
                android:layout_below="@+id/imageView"
                android:layout_alignParentLeft="true"
                />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:text=" Email --------------------"
                android:id="@+id/emailTxt"
                android:padding="5dp"
                android:layout_below="@+id/nameTxt"
                android:layout_alignParentLeft="true"
                />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:text="Username"
                android:id="@+id/usernameTxt"
                android:padding="2dp"
                android:textStyle="italic"
                android:layout_below="@+id/imageView"
                android:layout_alignParentRight="true" />

    </RelativeLayout>

</android.support.v7.widget.CardView>

activity_main.xml

This is our main activity layout. It will be inflated into our MainActivity layout.

We will have a LinearLayout at the root with a vertical orientation. This means that it will arrange its children vertically.

We also have a header TextView to display the header of our app, a GridView to render our data parsed from JSON and a button that when clicked initiates the download of the json data from the internet.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="info.camposha.kotlin_json.MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Verified Users"
        android:textAlignment="center"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textColor="@color/colorAccent" />

    <GridView
        android:id="@+id/myGridView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="0.5"
        android:numColumns="auto_fit" />

    <Button
        android:id="@+id/downloadBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:text="Download" />

</LinearLayout>

JSONDownloader.kt

This is our class responsible for downloading JSON data in the background thread via AsyncTask. We make our HTTP GET request using HttpURLConnection.

We are showing a progress dialog as our data downloads.

Derive From AsyncTask

So as to use AsyncTask which we said abstracts for us away thread details, we have to make our class extend the AsyncTask class which resides in the android.os package. We do this because AsyncTask is an abstract class and requires us to override he doInBackground() method:

class JSONDownloader() : AsyncTask<Void, Void, String>() {..}

But we will be passing some variables via the constructor, here’s how we do that in Kotlin:

class JSONDownloader(private var c: Context, private var jsonURL: String, private var myGridView: GridView)..

As you can see we are passing a Context object, a url leading us to our JSON download url and a GridView wehere we will bind our data.

Show ProgressDialog

We will be showing the progressDialog as our data downloads, however be aware that the ProgressDialog class is deprecated so you may use a progressbar if you like. However, we can still use ProgressBar currently and suppress the deprecation notices.

    override fun onPreExecute() {
        super.onPreExecute()

        pd = ProgressDialog(c)
        pd.setTitle("Download JSON")
        pd.setMessage("Downloading...Please wait")
        pd.show()
    }

Connect to Network via HttpURLConnection

Instead of using third part libraries, we will use the standard HttpURLConnection class to connect to our network.

First we instantiate the java.net.URL class passing in our url string.
We then open the a connection to that url using the openConnection() method of URL class and cast the returned URLConnection object to a HttpURLConnection.

We will be making a HTTP GET request. WE also set the connectTimeout, readTimeout and doInput properties. The we return the HttpURLConnection object.

    private fun connect(jsonURL: String): Any {
        try {
            val url = URL(jsonURL)
            val con = url.openConnection() as HttpURLConnection

            //CON PROPS
            con.requestMethod = "GET"
            con.connectTimeout = 15000
            con.readTimeout = 15000
            con.doInput = true

            return con

        } catch (e: MalformedURLException) {
            e.printStackTrace()
            return "URL ERROR " + e.message

        } catch (e: IOException) {
            e.printStackTrace()
            return "CONNECT ERROR " + e.message
        }
    }

Download JSON Data

We will now download the data given that we’ve established the connection. We first obtain an InputStream from our HttpURLConnection object and pass it to our BufferedInputStream constructor.

We then pass the BufferedInputStream object to an InputStreamReader object, which in turn we pass to our BufferedReader.

We instantiate a StringBuffer(or you can use a StringBuilder) onto which we will append our data.

private fun downlaod(): String {
        val connection = connect(jsonURL)
        if (connection.toString().startsWith("Error")) {
            return connection.toString()
        }
        //DOWNLOAD
        try {
            val con = connection as HttpURLConnection
            //if response is HTTP OK
            if (con.responseCode == 200) {
                //GET INPUT FROM STREAM
                val `is` = BufferedInputStream(con.inputStream)
                val br = BufferedReader(InputStreamReader(`is`))

                val jsonData = StringBuffer()
                var line: String?

                do {
                    line = br.readLine()

                    if (line == null){ break}

                    jsonData.append(line + "n");

                } while (true)

                //CLOSE RESOURCES
                br.close()
                `is`.close()

                //RETURN JSON
                return jsonData.toString()

            } else {
                return "Error " + con.responseMessage
            }
        } catch (e: IOException) {
            e.printStackTrace()
            return "Error " + e.message
        }
    }

Here’s the full source code for this JSONDownloader Kotlin class.

package info.camposha.kotlin_json
import android.app.ProgressDialog
import android.content.Context
import android.os.AsyncTask
import android.widget.GridView
import android.widget.Toast
import java.io.BufferedInputStream
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.MalformedURLException
import java.net.URL
@Suppress("DEPRECATION")
class JSONDownloader(private var c: Context, private var jsonURL: String, private var myGridView: GridView) : AsyncTask<Void, Void, String>() {
private lateinit var pd: ProgressDialog
/*
show dialog while downloading data
*/
override fun onPreExecute() {
super.onPreExecute()
pd = ProgressDialog(c)
pd.setTitle("Download JSON")
pd.setMessage("Downloading...Please wait")
pd.show()
}
/*
Perform Background downloading of data
*/
override fun doInBackground(vararg voids: Void): String {
return downlaod()
}
/*
When BackgroundWork Finishes, dismiss dialog and pass data to JSONParser
*/
override fun onPostExecute(jsonData: String) {
super.onPostExecute(jsonData)
pd.dismiss()
if (jsonData.startsWith("URL ERROR")) {
val error = jsonData
Toast.makeText(c, error, Toast.LENGTH_LONG).show()
Toast.makeText(c, "MOST PROBABLY APP CANNOT CONNECT DUE TO WRONG URL SINCE MALFORMEDURLEXCEPTION WAS RAISED", Toast.LENGTH_LONG).show()
}else  if (jsonData.startsWith("CONNECT ERROR")) {
val error = jsonData
Toast.makeText(c, error, Toast.LENGTH_LONG).show()
Toast.makeText(c, "MOST PROBABLY APP CANNOT CONNECT TO ANY NETWORK SINCE IOEXCEPTION WAS RAISED", Toast.LENGTH_LONG).show()
}
else {
//PARSE
Toast.makeText(c, "Network Connection and Download Successful. Now attempting to parse .....", Toast.LENGTH_LONG).show()
JSONParser(c, jsonData, myGridView).execute()
}
}
/*
Connect to network via HTTPURLConnection
*/
private fun connect(jsonURL: String): Any {
try {
val url = URL(jsonURL)
val con = url.openConnection() as HttpURLConnection
//CON PROPS
con.requestMethod = "GET"
con.connectTimeout = 15000
con.readTimeout = 15000
con.doInput = true
return con
} catch (e: MalformedURLException) {
e.printStackTrace()
return "URL ERROR " + e.message
} catch (e: IOException) {
e.printStackTrace()
return "CONNECT ERROR " + e.message
}
}
/*
Download JSON data
*/
private fun downlaod(): String {
val connection = connect(jsonURL)
if (connection.toString().startsWith("Error")) {
return connection.toString()
}
//DOWNLOAD
try {
val con = connection as HttpURLConnection
//if response is HTTP OK
if (con.responseCode == 200) {
//GET INPUT FROM STREAM
val `is` = BufferedInputStream(con.inputStream)
val br = BufferedReader(InputStreamReader(`is`))
val jsonData = StringBuffer()
var line: String?
do {
line = br.readLine()
if (line == null){ break}
jsonData.append(line + "n");
} while (true)
//CLOSE RESOURCES
br.close()
`is`.close()
//RETURN JSON
return jsonData.toString()
} else {
return "Error " + con.responseMessage
}
} catch (e: IOException) {
e.printStackTrace()
return "Error " + e.message
}
}
}

JSONParser.kt

This class is responsible for parsing the downloaded JSON data and rendering it into a GridView. We do this in a background thread using AsyncTask.

We show a progress dialog as our data is parsed. We use the native JSONObject and JSONArray classes to parse the data.

We also define ouf data object, which is User as an inner class here. The User class will define a single user.

Furthermore we define pur CustomAdapter class right here. The CustomAdapter will inflate our custom row_model.xml into a View object and bind our data to the custom views.

package info.camposha.kotlin_json
import android.app.ProgressDialog
import android.content.Context
import android.os.AsyncTask
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.GridView
import android.widget.TextView
import android.widget.Toast
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.util.ArrayList
@Suppress("DEPRECATION")
class JSONParser(private var c: Context, private var jsonData: String, private var myGridView: GridView) : AsyncTask<Void, Void, Boolean>() {
private lateinit var pd: ProgressDialog
private var users = ArrayList<User>()
override fun onPreExecute() {
super.onPreExecute()
pd = ProgressDialog(c)
pd.setTitle("Parse JSON")
pd.setMessage("Parsing...Please wait")
pd.show()
}
override fun doInBackground(vararg voids: Void): Boolean? {
return parse()
}
override fun onPostExecute(isParsed: Boolean?) {
super.onPostExecute(isParsed)
pd.dismiss()
if (isParsed!!) {
//BIND
myGridView.adapter = MrAdapter(c, users)
} else {
Toast.makeText(c, "Unable To Parse that data. ARE YOU SURE IT IS VALID JSON DATA? JsonException was raised. Check Log Output.", Toast.LENGTH_LONG).show()
Toast.makeText(c, "THIS IS THE DATA WE WERE TRYING TO PARSE :  "+ jsonData, Toast.LENGTH_LONG).show()
}
}
/*
Parse JSON data
*/
private fun parse(): Boolean {
try {
val ja = JSONArray(jsonData)
var jo: JSONObject
users.clear()
var user: User
for (i in 0 until ja.length()) {
jo = ja.getJSONObject(i)
val name = jo.getString("name")
val username = jo.getString("username")
val email = jo.getString("email")
user = User(username,name,email)
users.add(user)
}
return true
} catch (e: JSONException) {
e.printStackTrace()
return false
}
}
class User(private var m_username:String, private var m_name: String, private var m_email: String) {
fun getUsername(): String {
return m_username
}
fun getName(): String {
return m_name
}
fun getEmail(): String {
return m_email
}
}
class MrAdapter(private var c: Context, private var users: ArrayList<User>) : BaseAdapter() {
override fun getCount(): Int {
return users.size
}
override fun getItem(pos: Int): Any {
return users[pos]
}
override fun getItemId(pos: Int): Long {
return pos.toLong()
}
/*
Inflate row_model.xml and return it
*/
override fun getView(i: Int, view: View?, viewGroup: ViewGroup): View {
var convertView = view
if (convertView == null) {
convertView = LayoutInflater.from(c).inflate(R.layout.row_model, viewGroup, false)
}
val nameTxt = convertView!!.findViewById<TextView>(R.id.nameTxt) as TextView
val usernameTxt = convertView.findViewById<TextView>(R.id.usernameTxt) as TextView
val emailTxt = convertView.findViewById<TextView>(R.id.emailTxt) as TextView
val user = this.getItem(i) as User
nameTxt.text = user.getName()
emailTxt.text = user.getEmail()
usernameTxt.text = user.getUsername()
convertView.setOnClickListener { Toast.makeText(c,user.getName(),Toast.LENGTH_SHORT).show() }
return convertView
}
}
}

MainActivity.kt

Our MainActivity Kotlin class. Derives from AppCompatActivity.

We define the URL to our JSON data right here. Furthermmore when the download button is clicked, we instantiate our JSONDownloader class and execute it to start downloading our data.

package info.camposha.kotlin_json
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Button
import android.widget.GridView
class MainActivity : AppCompatActivity() {
private var jsonURL = "http://jsonplaceholder.typicode.com/users"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val gv = findViewById<GridView>(R.id.myGridView)
val downloadBtn = findViewById<Button>(R.id.downloadBtn)
downloadBtn.setOnClickListener({ JSONDownloader([email protected], jsonURL, gv).execute() })
}
}

Result

Kotlin Android GridView HttpURLConnection

Kotlin Android GridView HttpURLConnection

Kotlin Android GridView HttpURLConnection

Kotlin Android GridView HttpURLConnection

 

Best regards.