Android Networking with Coroutines and Retrofit

Retrofit is a type-safe HTTP client for Android. It essentially turns your HTTP API into a Java interface. In layman’s terms, it makes loading APIs into POJOs ridiculously simple. You simply need to create an interface and in it, annotate your API-calling functions and Retrofit automatically transforms it into the API call it needs to be.

Retrofit also offers URL Manipulation, Query Parameters, Request Bodies, and Multipart requests. It also works amazingly well with RxJava as API results can come in the form of Observable streams.

You can read about Retrofit in details here

For this tutorial, I will be using the JSON Placeholder API.

Dependencies

Add these dependencies to your app/build.gradle file. You can see the latest version here.

def retrofit_version = '2.6.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

def kotlin_version = "1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_version"

Add <uses-permission android:name="android.permission.INTERNET"/> to AndroidManifest.xml.

Defining the Model

We want to set up our model as a POJO (Plain Old Java Object). What this essentially means is a class with nothing but their variables and their getter/setter methods. In Kotlin, getter/setters are automatically defined so we don’t need to write them ourselves.

data class Post (
    var userId: Int = 0,
    var id: Int = 0,
    var title: String = "",
    @SerializedName("body")
    var text: String = ""
)

You want to match the variable names to the API fields. If you want to put a more fitting name for the variable in your app than the name in the API, annotate it with SerializedName("apifield") to match the variable to the API field.

Write the Interface

Create a new interface where you will place all your Retrofit calls.

interface JsonPlaceHolderApi {
    @GET("posts")
    suspend fun getPosts(): List<Post>
}

Each call is a function annotated with @GET("urlextension") or @POST("urlextension"), depending on the action you’re performing.

Build the Retrofit

A Retrofit is the object that makes all your Retrofit calls. In your main code, create a retrofit object using the builder, defining the Base URL of your APIs and the Converter Factory as the GsonConverterFactory.

val serviceApi = Retrofit.Builder()
    .baseUrl("https://jsonplaceholder.typicode.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(JsonPlaceHolderApi::class.java)

Make the Call

Testing the Retrofit suspend method is no different from other functions. Let’s see an example of it.

class PostViewModel(application: Application): AndroidViewModel(application) {
    private val job = SupervisorJob()
    private val coroutineContext = Dispatchers.IO + job
    private val serviceApi: JsonPlaceHolderApi

    init {
        serviceApi = Retrofit.Builder()
            .baseUrl("https://jsonplaceholder.typicode.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(JsonPlaceHolderApi::class.java)
    }

    fun getPosts() = viewModelScope.launch(coroutineContext) {
        val posts = serviceApi.getPosts()
        Log.d("TAG", "POSTS: $posts");
    }
}

Use PostViewModel in Activity like following example

class FriendsActivity: AppCompatActivity() {
    private lateinit var vm: PostViewModel
    private val TAG = "TAG"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_room)

        vm = ViewModelProviders.of(this).get(PostViewModel::class.java)
        vm.getPosts()
    }
}

Or without ViewModel

CoroutineScope(Dispatchers.IO).launch {
    val response = serviceApi.getPosts()
    withContext(Dispatchers.Main) {
        try {
            if (response.isSuccessful) {
                //Do something with response e.g show to the UI.
            } else {
                toast("Error: ${response.code()}")
            }
        } catch (e: HttpException) {
            toast("Exception ${e.message}")
        } catch (e: Throwable) {
            toast("Ooops: Something else went wrong")
        }
    }
}

To use response.isSuccessful you should change interface

interface JsonPlaceHolderApi {
    @GET("posts")
    suspend fun getPosts(): Response<List<Post>>
}