Network calls in android Using retrofit and coroutines

carlosokumu

Carlos Okumu

Posted on February 24, 2022

Network calls in android Using retrofit and coroutines

Making network calls is a common task in android.The commonly used Libraries are Retrofit and Volley.I am a huge fan of the retrofit Library because of the good documentation and it is very easy to setup and use.Today we are going to learn how to do network calls using this simple library and coroutines.
Starting from version 2.6.0 Retrofit supports the concept of “suspend” functions.
Before we begin,we are going to understand a simple project that we are going to build.
We will be using mvvm architecture pattern to help you understand our today's topic.If you are not familiar with mvvm or need a refresh of this,please check this nice article..

Set up a new project with Kotlin and other dependencies required
We will be using a single activity for the purpose of this tutorial.By default android studio creates an empty activity for you to begin with.

Add dependencies
Add the following dependencies in your app level build.gradle.

//Lifecycle

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-alpha03'
 implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0-alpha03'
 implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0-alpha03'

//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'

//Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
//Coil
 implementation 'io.coil-kt:coil:1.4.0'
Enter fullscreen mode Exit fullscreen mode

We have added the required dependencies and for the versions you can always change them as versions are being released with time

Setting up the network package
We are going to put our kotlin classes into packages to make things neat.First,let's set up the network package.

Create an Enum class,we will name it Status.Basically this class will represent the three states of the network call.So the Status.kt will look as below:

enum class Status {
    SUCCESS,
    ERROR,
    LOADING
}

Enter fullscreen mode Exit fullscreen mode

Let's now create a Generic class and name it ApiModel.This class will contain information about our network call so that we can update our views accordingly.So ApiModel.kt looks like below:

data class ApiModel<out T>(val status: Status, val data: T?, val message: String?) {
    companion object {
        fun <T> success(data: T): ApiModel<T> = ApiModel(status = Status.SUCCESS, data = data, message = null)

        fun <T> error(data: T?, message: String): ApiModel<T> =
            ApiModel(status = Status.ERROR, data = data, message = message)

        fun <T> loading(data: T?): ApiModel<T> = ApiModel(status = Status.LOADING, data = data, message = null)
    }
}
Enter fullscreen mode Exit fullscreen mode

Setting up our Model.
We will be fetching our data for the current weather using the name of a city.So go ahead and obtain an api key from here..If you find any problems please don't hesitate to reach out.
Now let's create our Pojo class to represent the data that we will get back after our api call.So again our WeatherResponse.kt
looks like so:

data class WeatherResponse(
    var location: Location,
    var current: Current
)


Enter fullscreen mode Exit fullscreen mode

Please note that i have not given the full content of the Location and Current classes but you can check them here in the models package.

Setting up Retrofit
Since we will be using retrofit for networking,let us create some classes for this purpose.First we will create an Interface class that will be a retrofit service class.So go ahead and create a class name it ApiService.kt then it should contain the following code.

interface ApiService {

    @GET("v1/current.json")
    suspend fun getCurrentWeather(@Query("key") key: String ="94d124c031ba4486b9c81847222402",@Query("q") cityName: String?): WeatherResponse

}

Enter fullscreen mode Exit fullscreen mode

then go ahead and create an object class and name it RetrofitBuilder.kt.As the name suggests,this class will be responsible for a creating our retrofit instance using the builder pattern.Have the following code in it

object RetrofitBuilder {



    private fun getRetrofit(): Retrofit {


        val interceptor = HttpLoggingInterceptor()
        interceptor.level = when (BuildConfig.BUILD_TYPE) {
            "release" -> HttpLoggingInterceptor.Level.NONE
            else -> HttpLoggingInterceptor.Level.BODY
        }


        val okHttp= OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(15, TimeUnit.SECONDS)
            .build()

        return Retrofit.Builder()
            .baseUrl(Constants.REST_BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttp)
            .build()
    }

    val apiService: ApiService = getRetrofit().create(ApiService::class.java)


}

Enter fullscreen mode Exit fullscreen mode

Since we will be using a Repository pattern,let's create a class and name it MainRepository.kt and we will pass it our retrofit instance to the constructor.Have the following code in it also.

class MainRepository(private val retrofit: RetrofitBuilder) {
    suspend fun fetchCurrentWeather() = retrofit.apiService.getCurrentWeather()
}

Enter fullscreen mode Exit fullscreen mode

Setting up the ViewModel
We are almost getting to the View now 🙂.Go ahead and create a class and name it MainViewModel.kt.As you have guessed, have the following code in it also:

class MainViewModel(private val mainRepository: MainRepository) : ViewModel() {


    fun fetchCurrentWeather() = liveData(Dispatchers.IO) {
        emit(ApiModel.loading(data = null))
        try {
            emit(ApiModel.success(data = mainRepository.fetchCurrentWeather()))
        } catch (exception: Exception) {
            emit(ApiModel.error(data = null, message = exception.message ?: "Something went Wrong"))
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You remember our Apimodel class we created earlier?,this is where we initialize our states.When loading,the data will be null,and if the call is sucessful,we have our data fetched into our value holder and also any error that occurred is tapped.We will also create a Factory for our viewmodel so go ahead and create a class named ViewmodelFactory.kt and have the following code in it:

class ViewModelFactory(private val retrofit: RetrofitBuilder) : 

ViewModelProvider.Factory {



    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
            return MainViewModel(MainRepository(retrofit)) as T
        }
        throw IllegalArgumentException("Unknown class name")
    }

}
Enter fullscreen mode Exit fullscreen mode

Let's now create the user interface.

Setting up the User Interface
First,let's understand how the interface will work,when a user enters the name of a city and the name is correct,the current weather conditions will be displayed in a CardView.
So activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <include
        android:id="@+id/appBar"
        layout="@layout/app_bar_layout" />


    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="@dimen/margin_normal"
        app:cardBackgroundColor="@color/white"
        app:cardCornerRadius="@dimen/margin_small">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">


            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="@dimen/margin_normal"
                    android:fontFamily="casual"
                    android:padding="@dimen/margin_normal"
                    android:text="Current Weather"
                    android:textColor="@color/black" />

                <Space
                    android:layout_width="0dp"
                    android:layout_height="0dp"
                    android:layout_gravity="center_vertical"
                    android:layout_weight="1"
                    android:minWidth="0dp" />

                <ImageView
                    android:id="@+id/imageIcon"
                    android:layout_width="44dp"
                    android:layout_height="44dp"
                    android:layout_margin="@dimen/margin_normal" />

            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/tempC"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="@dimen/margin_normal"
                    android:fontFamily="sans-serif-smallcaps"
                    android:padding="@dimen/margin_normal"
                    android:textColor="@color/black"
                    android:textSize="@dimen/margin_normal"
                    tools:text="20.3" />

                <Space
                    android:layout_width="0dp"
                    android:layout_height="0dp"
                    android:layout_gravity="center_vertical"
                    android:layout_weight="1"
                    android:minWidth="0dp" />

                <TextView
                    android:id="@+id/tempF"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="@dimen/margin_normal"
                    android:fontFamily="sans-serif-smallcaps"
                    android:padding="@dimen/margin_small"
                    android:textColor="@color/black"
                    android:textSize="@dimen/margin_normal"
                    tools:text="25.7" />


            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-thin"
                    android:padding="@dimen/margin_small"
                    android:textColor="@color/black"
                    android:textSize="@dimen/margin_normal"
                    tools:text="Temp(C)" />

                <Space
                    android:layout_width="0dp"
                    android:layout_height="0dp"
                    android:layout_gravity="center_vertical"
                    android:layout_weight="1"
                    android:minWidth="0dp" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-thin"
                    android:padding="@dimen/margin_small"
                    android:textColor="@color/black"
                    android:textSize="@dimen/margin_normal"
                    tools:text="Temp(f)" />


            </LinearLayout>

            <TextView
                android:id="@+id/txtWeather"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_margin="@dimen/margin_small"
                android:fontFamily="sans-serif-thin"
                android:textStyle="normal"
                tools:text="Cloudy" />
        </LinearLayout>

    </androidx.cardview.widget.CardView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Enter fullscreen mode Exit fullscreen mode

then app_bar_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout 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"
    android:elevation="@dimen/app_bar_elevation"
    android:gravity="center_vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:minHeight="@dimen/toolbar_height"
        app:contentInsetStart="@dimen/toolbar_inset_start"
        app:contentInsetStartWithNavigation="@dimen/toolbar_inset_start">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="@dimen/margin_small"
            android:gravity="center_vertical"
            android:orientation="vertical">

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="start"
                android:fontFamily="casual"
                android:singleLine="true"
                android:textSize="@dimen/toolbar_title_text_size"
                tools:text="Toolbar title" />

            <TextView
                android:id="@+id/subtitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="start"
                android:singleLine="true"
                android:visibility="gone"
                tools:text="Toolbar subtitle"
                tools:visibility="visible" />

        </LinearLayout>

    </androidx.appcompat.widget.Toolbar>

</com.google.android.material.appbar.AppBarLayout>
Enter fullscreen mode Exit fullscreen mode

And Finally our MainActivity.kt will look us below:

class MainActivity : AppCompatActivity() {


    private lateinit var searchView: SearchView
    private lateinit var searchItem: MenuItem
    private lateinit var viewModel: MainViewModel
    private lateinit var weatherIcon: ImageView
    private lateinit var txtWeather: TextView
    private lateinit var tempF: TextView
    private lateinit var tempC: TextView


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Toolbar>(R.id.toolbar).inflateMenu(R.menu.menu_search)

        searchItem = findViewById<AppBarLayout>(R.id.appBar).findViewById<Toolbar>(R.id.toolbar).menu.findItem(
            R.id.search_item
        )
        searchView = searchItem.actionView as SearchView


        viewModel = ViewModelProviders.of(
            this,
            ViewModelFactory(RetrofitBuilder)
        ).get(MainViewModel::class.java)

        weatherIcon = findViewById(R.id.imageIcon)
        txtWeather = findViewById(R.id.txtWeather)
        tempF = findViewById(R.id.tempF)
        tempC = findViewById(R.id.tempC)

        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String?): Boolean {
                viewModel.fetchCurrentWeather(query).observe(this@MainActivity,::onResponse)
                return true
            }

            override fun onQueryTextChange(newText: String?): Boolean {
                return true
            }

        })

    }

    private fun onResponse(apiModel: ApiModel<WeatherResponse>?) {
        when(apiModel?.status){
            Status.ERROR -> {
                Toast.makeText(this, apiModel.message, Toast.LENGTH_SHORT).show()
            }
            Status.SUCCESS -> {
                val data = apiModel.data
                weatherIcon.load("https:" + apiModel.data?.current?.condition?.icon)

                txtWeather.text = data?.current?.condition?.text
                tempC.text = data?.current?.temp_c.toString()
                tempF.text = data?.current?.temp_f.toString()
            }
            Status.LOADING -> {
                Toast.makeText(this, "Loading", Toast.LENGTH_SHORT).show()
            }
        }
    }


}
Enter fullscreen mode Exit fullscreen mode

Wrapping up
This was a quick guide on how you can use retrofit and coroutines in android to easily make an api call.You can check out the full code in my repository here.Happy Coding!

💖 💪 🙅 🚩
carlosokumu
Carlos Okumu

Posted on February 24, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024

How to Use KitOps with MLflow
beginners How to Use KitOps with MLflow

November 29, 2024

Modern C++ for LeetCode 🧑‍💻🚀
leetcode Modern C++ for LeetCode 🧑‍💻🚀

November 29, 2024