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.
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:
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.
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 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 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 setup 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 = [email protected]
}
}
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:
Conclusion
In this post, we have learnt 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 get play around the repo here and feel free to drop your feedback.
The source code to the GitHub repo is here.
The post Handling connectivity errors in Android apps with Kotlin appeared first on Pusher Blog.