Kotlin Note: Retrofit

Most mobile applications nowadays fetch data from remote APIs. When building a similar application in Android, you might wonder what library to use for this purpose. One popular option you might want to consider is Retrofit.

In this Note, I'll show the most basic example of using Retrofit in a newly created Android (Kotlin) project.

We will be making a simple app that displays a list of todos taken from this API. Follow the instructions below to start.

  1. Create a new Android project with Empty Activity. Make sure to set the language to Kotlin and enable Use androidx.* artifacts option.
  2. Import Retrofit and its complementary libraries (Gson and Gson converter) to the project by modifying the module-level build.gradle like this:
    dependencies {
        ... other dependencies
    
        implementation 'com.squareup.retrofit2:retrofit:2.6.0'
        implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
        implementation 'com.google.code.gson:gson:2.8.5'
    }​

    When writing this, the latest Retrofit version is 2.6.0. Make sure to check the latest version here and always use the latest version. The same applies to the rest of the libraries.

  3. Add permission to use the Internet in AndroidManifest.xml:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.hanmajid.kotlintutorialboilerplate">
    
        <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>​​

  4. Create an XML layout for todo item named todo_item.xml like this:
    <?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="wrap_content">
    
        <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                tools:text="Title"
                android:id="@+id/title" app:layout_constraintTop_toTopOf="parent"
                android:layout_marginTop="16dp" app:layout_constraintStart_toStartOf="parent"
                android:layout_marginLeft="16dp" android:layout_marginStart="16dp"
                android:textSize="24sp"/>
        <TextView
                tools:text="Completed"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" android:id="@+id/completed"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toBottomOf="@+id/title"
                app:layout_constraintStart_toStartOf="@+id/title"
        />
        <View
                android:id="@+id/divider"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="?android:attr/listDivider"
                tools:layout_editor_absoluteX="-16dp" app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/completed" android:layout_marginTop="16dp"
        />
    </androidx.constraintlayout.widget.ConstraintLayout>​

    The layout will look like this:

    Preview of todo_item.xml

  5. Change the content of activity_main.xml to be like this:
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
        <androidx.recyclerview.widget.RecyclerView
                tools:listitem="@layout/todo_item"
                android:layout_width="match_parent"
                android:layout_height="match_parent" android:id="@+id/recycler_view"/>
    </androidx.constraintlayout.widget.ConstraintLayout>​

    Here's what activity_main.xml looks like now:
    Preview of activity_main.xml

  6. For simplicity's sake, the rest of the code will be declared inside MainActivity.kt. In a real application, you might want to separate every new class (Todo, TodoAdapter, TodoService) into a new file.
    Here's the content of the MainActivity.kt:
    package com.hanmajid.kotlintutorialboilerplate
    
    import android.os.Bundle
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.TextView
    import androidx.appcompat.app.AppCompatActivity
    import androidx.recyclerview.widget.LinearLayoutManager
    import androidx.recyclerview.widget.RecyclerView
    import retrofit2.Call
    import retrofit2.Callback
    import retrofit2.Response
    import retrofit2.Retrofit
    import retrofit2.converter.gson.GsonConverterFactory
    import retrofit2.http.GET
    
    class MainActivity : AppCompatActivity() {
        private lateinit var recyclerView: RecyclerView
        private var data: ArrayList<Todo> = ArrayList()
        private lateinit var adapter: RecyclerView.Adapter<*>
        private lateinit var manager: RecyclerView.LayoutManager
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            var retrofit: Retrofit = Retrofit.Builder()
                .baseUrl("https://jsonplaceholder.typicode.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
    
            manager = LinearLayoutManager(this)
            adapter = TodoAdapter(data)
    
            recyclerView = findViewById(R.id.recycler_view)
            recyclerView.layoutManager = manager
            recyclerView.adapter = adapter
    
            val todoService: TodoService = retrofit.create(TodoService::class.java)
            val list = todoService.listTodos()
            list.enqueue(object: Callback<List<Todo>> {
                override fun onFailure(call: Call<List<Todo>>, t: Throwable) {
    
                }
    
                override fun onResponse(call: Call<List<Todo>>, response: Response<List<Todo>>) {
                    if(response.code() === 200) {
                        data.addAll(response.body()!!)
                        recyclerView.adapter!!.notifyDataSetChanged()
                    }
                }
    
            })
        }
    }
    
    class TodoAdapter(private var data: List<Todo>): RecyclerView.Adapter<TodoAdapter.ViewHolder>() {
    
        class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
            var title: TextView = view.findViewById(R.id.title)
            var completed: TextView = view.findViewById(R.id.completed)
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            return ViewHolder(
                LayoutInflater.from(parent.context).inflate(R.layout.todo_item, parent, false)
            )
        }
    
        override fun getItemCount() = data.size
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.title.text = data[position].title
            holder.completed.text = if(data[position].completed) "Completed" else "Not Completed"
        }
    
    }
    
    interface TodoService {
        @GET("todos")
        fun listTodos(): Call<List<Todo>>
    }
    
    class Todo (
        val id: Integer,
        val userId: Integer,
        val title: String,
        val completed: Boolean
    )​

  7. That's it! Now run your application and see the result for yourself. It should look like this:
    Final look of the application

 

That's it for now!

· Recent posts ·