Skip to content

Using Retrofit with Coroutines (Oversimplified)

This is a short one, because there’s only one thing you need to do to make a Retrofit call to work with coroutines.

@GET("/")
suspend fun getQuote(): QuoteResponse

Add the suspend keyword and you’re sorted. Call it from other suspend functions and you’re done.

That’s the main piece of information you need to know. Everything from here is going to be a quick tutorial on how to go from an entirely new app to having an app that uses coroutines to call a network API and display it to the user.

And we are using the very blessed Kanye.REST API.

Do note that we’re doing this oversimplified, so no dagger, no factrory classes, and we’re following a very loose MVVM model.

Then if you’re using this as the basis for an app you’re creating seriously for production purposes, I’ll leave it to you to do the optimisations for production code quality. All I want to do is get the idea across.

Gradle Dependencies

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation"com.squareup.moshi:moshi-kotlin:1.12.0"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"

We’ll need the two higher dependencies for our coroutine functionality, and the lower three for our network call that we’ll do using Retrofit and Moshi. Add these under your app/build.gradle dependencies.

plugins {
    ...
    id 'kotlin-android-extensions'
}

And at the top of the file, add the kotlin-android-extensions plugin just to give you an easier time to access view elements without having to use data binding or view binding.

Building the Classes

Let’s just blast through them.

QuoteResponse

data class QuoteResponse(
    val quote: String
)

This is the data response class we’re basing off of the Kanye.REST API.

KanyeApi

interface KanyeApi {

    @GET("/")
    suspend fun getQuote(): QuoteResponse

}

Our Retrofit interface. We’re making it a suspend function so that it works well in a coroutine and can be called asynchronously from other suspend functions.

KanyeRepository

class KanyeRepository {

    private val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

    private val kanyeApi = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .baseUrl("https://api.kanye.rest/")
        .build()
        .create(KanyeApi::class.java)

    suspend fun getQuote() = kanyeApi
        .getQuote()
        .quote

}

We are of course calling our API from another suspend function, and extracting quote as a String to return to the caller.

MainViewModel

class MainViewModel: ViewModel() {

    private val kanyeRepository = KanyeRepository()

    val quoteLiveData = MutableLiveData<String>()

    init {
        fetchQuote()
    }

    fun fetchQuote() {
        viewModelScope.launch(Dispatchers.IO) {
            val quote = kanyeRepository.getQuote()
            quoteLiveData.postValue(quote)
        }
    }
}

The launch is simple and fulfils all our requirements for this simple app, so we’re not going to use anything more complex than it. Since it’s a network call, we’ll dispatch it on the IO thread, get the quote, and post it to the live data.

Since we are also using a view model, using viewModelScope will tie the coroutine’s lifetime to that of the view model, so we’ve also taken measures against memory leaks this way.

activity_main.xml

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

    <TextView
        android:id="@+id/quote_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:gravity="center_horizontal"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:textColor="#1a1a1a"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="Kanyeet" />

    <Button
        android:id="@+id/generate_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Generate"
        android:layout_marginTop="16dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/quote_text" />

</androidx.constraintlayout.widget.ConstraintLayout>

We have a Button and a TextView. The TextView displays the quote which is fetched on app launch (via MainViewModel.init), but we’ll also have the Button to generate a new quote.

MainActivity

class MainActivity : AppCompatActivity() {

  private val viewModel = MainViewModel()

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

  private fun observeQuote() {
    viewModel.quoteLiveData.observe(this) { quote ->
      quote_text.text = quote
    }
  }

  private fun setButtonListener() {
    generate_button.setOnClickListener {
      viewModel.fetchQuote()
    }
  }
}

Finally, our Activity. We observe the live data held in the view model, and update the TextView accordingly.

Run the App

That’s all we need. Run the app, and there you have it, a simple Kanye West quote generator.

That just proves how simple it is to use coroutines together with Retrofit. No need for special converter factories that Jake Wharton usually creates for us anyway, it’s all built into Retrofit itself and we just need to use it like so.

I hope you gained something from this little post. Over the past couple of weeks, I’ve been more active on my socials than I ever have before, so I’d appreciate a follow! You might just find content you’ll enjoy as a fellow coder, on Instagram especially. The links to all of them are on the sidebar.

If you want to see the source code the code from this post is based from, check out my Kanyeet repository.

And as always, happy coding ༼ つ ◕_◕ ༽つ