Retrofit Android Example using Kotlin language
Retrofit is http client used for android. This post shows you how to use retrofit networking library using kotlin in your android app. You can also checkout sample app at HERE.
Retrofit is type-safe REST client for Android which aims to make it easier to consume RESTful web services. Retrofit automatically serializes the JSON response using a POJO(Plain Old Java Object) which must be defined in advanced for the JSON Structure. In Retrofit2 you configure which converter is used for the data serialization. Typically for JSON you use GSon, but you can add custom converters to process XML or other protocols. Retrofit uses the OkHttp library for HTTP requests.
If you want to use retrofit2 library with mvvm architecture pattern, You can refer this article.
Add Internet Permission in the AndroidManifest.xml
Internet permission is required to call apis in android applications. You need to add required permissions in manifest file.
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET"/>
Add the below dependencies in your app level build.gradle file
dependencies { implementation 'com.squareup.retrofit2:retrofit:2.5.0' implementation 'com.squareup.retrofit2:converter-gson:2.5.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1' implementation 'com.android.support:recyclerview-v7:28.1.1' implementation 'com.android.support:cardview-v7:28.1.1' }
Create a ApiClient class
We are using retrofit builder class to create retrofit instance with base url of api in this api client class. HttpLoggingInterceptor is used to display logs related to api call. Retrofit2 does not include convertor. So we manually add popular Gson convertor for JSON mapping.
package com.example.retrofitsample.api import com.google.gson.Gson import com.google.gson.GsonBuilder import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory object ApiClient { private const val BASE_URL: String = "https://jsonplaceholder.typicode.com/" private val gson : Gson by lazy { GsonBuilder().setLenient().create() } private val interceptor : HttpLoggingInterceptor by lazy { HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) } private val httpClient : OkHttpClient by lazy { OkHttpClient.Builder().addInterceptor(interceptor).build() } private val retrofit : Retrofit by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .client(httpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .build() } val apiService : ApiService by lazy{ retrofit.create(ApiService::class.java) } }
Create an interface for different API calls
ApiService interface defines api endpoints with retrofit annotations about the various parameters and request method. We also need to specify return type for specific type response. Otherwise we can also define return type as Call<ResponseBody>.
package com.example.retrofitsample.api import com.example.retrofitsample.response.User import retrofit2.Call import retrofit2.http.GET interface ApiService { @GET("users") fun getUsers(): Call<MutableList<User>> }
Create a response class to parse json data
package com.example.retrofitsample.response import com.google.gson.annotations.SerializedName import java.io.Serializable data class User( @SerializedName("id") val id: Int? = null, @SerializedName("name") val name: String? = null, @SerializedName("username") val userName: String? = null, @SerializedName("email") val email: String? = null, @SerializedName("address") val addressObject : Address? = null, @SerializedName("phone") val phone: String? = null, @SerializedName("website") val website: String? = null ) : Serializable
package com.example.retrofitsample.response import com.google.gson.annotations.SerializedName import java.io.Serializable data class Address( @SerializedName("street") val street: String? = null, @SerializedName("suite") val suite: String? = null, @SerializedName("city") val city: String? = null, @SerializedName("zipcode") val zipCode: String? = null ) : Serializable
Create a layout file to display RecyclerView in Activity
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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=".activities.MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_main" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Create a layout file for adapter to display list row
<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" app:cardCornerRadius="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:layout_marginStart="15dp" android:layout_marginEnd="15dp"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp"> <TextView android:id="@+id/txt_user_name" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="22sp" android:textStyle="bold" /> <TextView android:id="@+id/txt_user_info1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/txt_user_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" /> <TextView android:id="@+id/txt_user_info2" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/txt_user_info1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" /> <TextView android:id="@+id/txt_user_address" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/txt_user_info2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView>
Create a adapter class to bind view and data
package com.example.retrofitsample.adapters import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.example.retrofitsample.R import com.example.retrofitsample.response.User class UsersAdapter(private val context: Context, private var list: MutableList<User>) : RecyclerView.Adapter<UsersAdapter.MyViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val inflater = LayoutInflater.from(context) val view: View = inflater.inflate(R.layout.user_row,parent,false) return MyViewHolder(view) } override fun getItemCount(): Int { return list.size } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val user = list.get(position) holder.name?.text = user.name holder.info1?.text = user?.userName + " | " + user?.email holder.info2?.text = user?.phone + " | " + user?.website val addressObj = user.addressObject holder.address?.text = addressObj?.suite + "," + addressObj?.street + "," + addressObj?.city + "," + addressObj?.zipCode } class MyViewHolder(var view: View) : RecyclerView.ViewHolder(view){ var name: TextView? = null var info1: TextView? = null var info2: TextView? = null var address: TextView? = null init { name = view.findViewById(R.id.txt_user_name) info1 = view.findViewById(R.id.txt_user_info1) info2 = view.findViewById(R.id.txt_user_info2) address = view.findViewById(R.id.txt_user_address) } } }
Create a util class to define some functions to use for API
package com.example.retrofitsample.utils import android.R import android.app.Activity import android.content.Context import android.graphics.Color import android.net.ConnectivityManager import android.view.Gravity import android.view.View import android.view.ViewGroup import android.widget.ProgressBar import android.widget.RelativeLayout import android.widget.TextView import android.widget.Toast object Utility { private var progressBar: ProgressBar? = null fun Context.isInternetAvailable(): Boolean { try { val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val netInfo = cm.activeNetworkInfo return if (netInfo != null && netInfo.isConnected) { true } else { showErrorToast("Internet not available. Please try again!!") false } } catch (e: Exception) { e.printStackTrace() } return false } fun Context.showErrorToast(message: String?) { try { val toast = Toast.makeText(this, message, Toast.LENGTH_LONG) // set message color val textView = toast.view.findViewById(android.R.id.message) as? TextView textView?.setTextColor(Color.WHITE) textView?.gravity = Gravity.CENTER // set background color toast.view.setBackgroundColor(Color.RED) toast.setGravity(Gravity.TOP or Gravity.FILL_HORIZONTAL, 0, 0) toast.show() } catch (e: Exception) { e.printStackTrace() } } // show progressbar fun Context.showProgressBar() { try { val layout = (this as? Activity)?.findViewById<View>(android.R.id.content)?.rootView as? ViewGroup progressBar = ProgressBar(this, null, R.attr.progressBarStyleLarge) progressBar?.let { it1 -> it1.isIndeterminate = true val params = RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT ) val rl = RelativeLayout(this) rl.gravity = Gravity.CENTER rl.addView(it1) layout?.addView(rl, params) it1.visibility = View.VISIBLE } } catch (e: Exception) { e.printStackTrace() } } // hide progressbar fun hideProgressBar() { try { progressBar?.let { it.visibility = View.GONE } } catch (e: Exception) { e.printStackTrace() } } }
Define a method to call API in Activity
package com.example.retrofitsample.activities import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import androidx.recyclerview.widget.LinearLayoutManager import com.example.retrofitsample.R import com.example.retrofitsample.adapters.UsersAdapter import com.example.retrofitsample.api.ApiClient import com.example.retrofitsample.response.User import com.example.retrofitsample.utils.Utility.hideProgressBar import com.example.retrofitsample.utils.Utility.isInternetAvailable import com.example.retrofitsample.utils.Utility.showProgressBar import kotlinx.android.synthetic.main.activity_main.* import retrofit2.Call import retrofit2.Callback import retrofit2.Response class MainActivity : AppCompatActivity() { private var listUsers: MutableList<User> = mutableListOf<User>() private var adapter: UsersAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) listUsers = mutableListOf() recycler_main.layoutManager = LinearLayoutManager(this@MainActivity) adapter = UsersAdapter( this, listUsers ) recycler_main.adapter = adapter if (isInternetAvailable()) { getUsersData() } } private fun getUsersData() { showProgressBar() ApiClient.apiService.getUsers().enqueue(object : Callback<MutableList<User>> { override fun onFailure(call: Call<MutableList<User>>, t: Throwable) { hideProgressBar() Log.e("error", t.localizedMessage) } override fun onResponse( call: Call<MutableList<User>>, response: Response<MutableList<User>> ) { hideProgressBar() val usersResponse = response.body() listUsers.clear() usersResponse?.let { listUsers.addAll(it) } adapter?.notifyDataSetChanged() } }) } }
This is a very good & useful article for retrofit2 in kotlin.
Fabulous example
Good article