In this article, we will see how we can monitor internet connection changes. When the phone is online, we will fetch data from an API, otherwise, we will display an error page. Here’s what we want to create:

App Demo

Many applications require an internet connection to either fetch or save data. However, it’s normal for internet connection to be lost every now and then. As a developer, it falls to you to make sure the user has a smooth experience when using our applications and the internet connection is lost.

Prerequisites

In other to follow the tutorial, you need the following:

  • Android Studio (version >= 3.x recommended). Download here.
  • Basic knowledge of Kotlin.

Building our application

Launch Android Studio and create a ‘New Project…’ using the wizard. You should choose the “Basic Activity” template and select your preferred target, we are using (API 26: Android 8.0). You should also enable Kotlin support for the project.

Create project dialog

Asides the usual dependencies that come with a new project, we need to add some dependencies. One of the dependencies we need to add is Retrofit. Retrofit is a client for making HTTP calls.

Open your build.gradle file and add the following dependencies:

    implementation 'com.android.support:design:27.1.1'
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'

Sync the gradle file so that the dependencies will be downloaded.

Next, we will request two permissions:

  • Internet permission – to gain access to make network calls
  • Network state – to check the network state of a device so that we know when we have an internet connection or not.

Open your AndroidManifest.xml file and add the permissions like so:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.android.internetconnectivity">

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

        [...]

    </manifest>

When there is a network connection, we will fetch data from an API. Let’s set up an interface to hold the endpoints we will access. Create a new Kotlin file named ApiService and paste this:

    import retrofit2.Call
    import retrofit2.http.GET

    interface ApiService {
        @GET(".")
        fun getFeeds(): Call<String>
    }

For this demo, we are only going to access one endpoint, which is equivalent to our base URL. It’s for this reason we used a dot instead of the usual /some-url in the @GET annotation.

When these items are fetched, we will display the items in a list. We, therefore, need a RecyclerView in the layout and a matching adapter. Create a new Kotlin file named RecyclerAdapter and paste this:

    import android.support.v7.widget.RecyclerView
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.TextView

    class RecyclerAdapter : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {

        private var list = ArrayList<String>()

        fun setItems(newList: ArrayList<String>){
            this.list = newList
            this.notifyDataSetChanged()
        }

        override fun getItemCount() = list.size

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val view = LayoutInflater.from(parent.context)
                    .inflate(android.R.layout.simple_list_item_1, parent, false)

            return ViewHolder(view)
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.textView.text = list[position]
        }

        inner class ViewHolder(itemView: View?): RecyclerView.ViewHolder(itemView) {
            var textView: TextView = itemView!!.findViewById(android.R.id.text1)
        }

    }

The adapter handles the display of items on a list. It has some overridden methods like:

  • getItemCount – to tell the size of the list to be populated.
  • onCreateViewHolder – used to choose a layout for a list row.
  • onBindViewHolder – to bind data to each row depending on the position, etc.

Next, we will update the layout of our MainActivity‘s activity_main.xml file like so:

    <?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"
        tools:context=".MainActivity">

        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/recyclerView"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/no_internet_connection" />

    </android.support.constraint.ConstraintLayout>

The layout contains a RecyclerView for our list items and an ImageView to show an error message.

We also need an error message image. Once you have an image, rename the file to no_internet_connection and save it to your drawable folder: NameOfProject/app/src/main/res/drawable.

For us to monitor when the connectivity changes, we need broadcast receivers. Broadcast receivers are components that allow you to register and listen to Android system and application events. Usually, the Android system sends broadcast events when various system events occur and your app needs to register to get these events.

Let’s register a listener to be triggered when the internet connection is online or offline. Open your MainActivity file and paste the following code:

    import android.content.BroadcastReceiver
    import android.content.Context
    import android.content.Intent
    import android.content.IntentFilter
    import android.net.ConnectivityManager
    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import android.support.v7.widget.LinearLayoutManager
    import android.util.Log
    import android.view.View
    import kotlinx.android.synthetic.main.activity_main.*
    import okhttp3.OkHttpClient
    import org.json.JSONObject
    import retrofit2.Call
    import retrofit2.Callback
    import retrofit2.Response
    import retrofit2.Retrofit
    import retrofit2.converter.scalars.ScalarsConverterFactory


    class MainActivity : AppCompatActivity() {

        private val arrayList = ArrayList<String>()
        private val adapter = RecyclerAdapter()
        private val retrofit = Retrofit.Builder()
                .baseUrl("https://api.reddit.com/")
                .addConverterFactory(ScalarsConverterFactory.create())
                .client(OkHttpClient.Builder().build())
                .build()

        private var broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                val notConnected = intent.getBooleanExtra(ConnectivityManager
                        .EXTRA_NO_CONNECTIVITY, false)
                if (notConnected) {
                    disconnected()
                } else {
                    connected()
                }
            }
        }

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            setupRecyclerView()
        }

    }

Above, we initialized some variables:

  • arrayList – we will add fetched items to this list.
  • adapter – this is the instance of the adapter class.
  • retrofit – a Retrofit instance.
  • broadcastReciever – this instance implements the onRecieve callback. This callback method is called when the system has notified us of a change in the network connection. In the callback, we then check to know the connectivity status thereby calling either a private connected or disconnected function.

After creating the broadcast receiver, we have to register it to get updates and unregister if there are no more activities. To do this, add the following functions to the code above in the MainActivity:

    override fun onStart() {
        super.onStart()
        registerReceiver(broadcastReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
    }

    override fun onStop() {
        super.onStop()
        unregisterReceiver(broadcastReceiver)
    }

In the onCreate function, we set up our RecyclerView by calling the setupRecyclerView. Create a private function in the MainActivity class and set it up like this:

    private fun setupRecyclerView(){
        with(recyclerView){
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = this@MainActivity.adapter
        }
    }

Remember we mentioned the connected and disconnected functions earlier in this post. We will now add them to the class. Add them to the MainActivity file like so:

    private fun disconnected() {
        recyclerView.visibility = View.INVISIBLE
        imageView.visibility = View.VISIBLE
    }

    private fun connected() {
        recyclerView.visibility = View.VISIBLE
        imageView.visibility = View.INVISIBLE
        fetchFeeds()
    }

The disconnected function is called when there is no network connection. It hides the RecyclerView and shows the ImageView. The connected function is called when there is an active internet connection. It shows the RecyclerView, hides the ImageView, and finally calls the fetchFeeds function.

Next, in the same file, paste the following code:

    private fun fetchFeeds() {
        retrofit.create(ApiService::class.java)
                .getFeeds()
                .enqueue(object : Callback<String> {
                    override fun onFailure(call: Call<String>, t: Throwable) {
                        Log.e("MainActivityTag", t.message)
                    }

                    override fun onResponse(call: Call<String>?, response: Response<String>) {
                        addTitleToList(response.body()!!)
                    }

                })
    }

This function calls the API to get data. When the call is successful, we have another function that helps us add the title of the posts gotten from the endpoint to our list and then to our adapter. Create a function named addTitleToList and set it up like so:

    private fun addTitleToList(response: String) {
        val jsonObject = JSONObject(response).getJSONObject("data")
        val children = jsonObject.getJSONArray("children")

        for (i in 0..(children.length()-1)) {
            val item = children.getJSONObject(i).getJSONObject("data").getString("title")
            arrayList.add(item)
            adapter.setItems(arrayList)
        }
    }

We manually parsed the JSON here to get the title. When you ‘Run’ the app, you should have this:

App Demo

Conclusion

In this post, we have learned how to monitor network changes using broadcast receivers. We saw how we register and unregister for broadcasts sent by the Android system. You have gained some valuable knowledge on handling network errors in your app. You can play around the repo here and feel free to drop your feedback.

The source code to the GitHub repo is here.