• Android Networking in 2019 — Retrofit with Kotlin’s Coroutines
  • By Navendra Jha
  • Translation from: The Gold Project
  • This article is permalink: github.com/xitu/gold-m…
  • Translator: feximin

Android Web of 2019 — Retrofit with Kotlin coroutines

2018 has seen a lot of dramatic changes in the Android world, especially when it comes to the Android web. The release of a stable version of Kotlin coroutines has given Android a major boost in the way it handles multithreading from RxJava to Kotlin coroutines. In this article, we’ll discuss using Retrofit2 and Kotlin coroutines to make network API calls in Android. We’ll call the TMDB API to get a list of the top movies.

I know the concept, show me the code!!

If you have experience with the Android web and made network calls before using Retrofit, but probably used RxJava instead of Kotlin coroutines, and you just want to see the implementation, check out the ReadMe file on Github.

Android Network Overview

In short, the Android network, or any network, works like this:

  • Request – Makes an HTTP request to a URL (terminal) with the correct header information, usually with an authorized Key if needed.
  • Response – The request returns an error or a successful response. In the case of success, the response contains the contents of the terminal (usually in JSON format).
  • Parse and store – Parse JSON and get the required values, then store them in the data class.

On Android, we use:

  • Okhttp – Used to create HTTP requests with appropriate headers.
  • Retrofit — Send the request.
  • Moshi/ GSON — Parses JSON data.
  • Kotlin coroutines – Used to issue non-blocking (main thread) network requests.
  • Picasso/Glide – Download a web image and set it to ImageView.

Obviously these are just some of the hot libraries, but there are others. In addition, these libraries were developed by the great people at Square. Check out the Square team’s open source project for more.

Let’s start

The Movie Database (TMDb) API contains a list of all popular, upcoming, and ongoing movies and TV shows. This is also one of the most popular apis.

The TMDB API requires an API key to request. To do this:

  • Set up an account on TMDB
  • Follow these steps to register an API key.

Hide API keys in your version control system (optional but recommended)

After obtaining the API key, follow these steps to hide it in the VCS.

  • Add your key to the local.properties file in the root directory.
  • Use code to access keys in build.gradle.
  • You can then use the key in your program with BuildConfig.
//In local.properties
tmdb_api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxx"

//In build.gradle (Module: app)
buildTypes.each {
        Properties properties = new Properties()
        properties.load(project.rootProject.file("local.properties").newDataInputStream())
        def tmdbApiKey = properties.getProperty("tmdb_api_key"."")

        it.buildConfigField 'String'."TMDB_API_KEY", tmdbApiKey
        
        it.resValue 'string'."api_key", tmdbApiKey

}

//In your Constants File
var tmdbApiKey = BuildConfig.TMDB_API_KEY
Copy the code

Setting up the project

To set up the project, we first add all the required dependencies to the build.gradle (Module: app) file:

// build.gradle(Module: app)
dependencies {

    def moshiVersion="1.8.0 comes with"
    def retrofit2_version = "2.5.0"
    def okhttp3_version = "3.12.0"
    def kotlinCoroutineVersion = "1.0.1"
    def picassoVersion = "2.71828"

     
    //Moshi
    implementation "com.squareup.moshi:moshi-kotlin:$moshiVersion"
    kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"

    //Retrofit2
    implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
    implementation "com.squareup.retrofit2:converter-moshi:$retrofit2_version"
    implementation "Com. Jakewharton. Retrofit: retrofit2 - kotlin - coroutines - adapter: 0.9.2"

    //Okhttp3
    implementation "com.squareup.okhttp3:okhttp:$okhttp3_version"
    implementation 'com. Squareup. Okhttp3: logging - interceptor: 3.11.0'
    
     //Picasso for Image Loading
    implementation ("com.squareup.picasso:picasso:$picassoVersion") {exclude group: "com.android.support"
    }

    //Kotlin Coroutines
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutineVersion"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutineVersion"

   
}
Copy the code

Now create our TmdbAPI service

//ApiFactory to create TMDB Api
object Apifactory{
  
    //Creating Auth Interceptor to add api_key query in front of all the requests.
    private val authInterceptor = Interceptor {chain->
            val newUrl = chain.request().url()
                    .newBuilder()
                    .addQueryParameter("api_key", AppConstants.tmdbApiKey)
                    .build()

            val newRequest = chain.request()
                    .newBuilder()
                    .url(newUrl)
                    .build()

            chain.proceed(newRequest)
        }
  
   //OkhttpClient for building http request url
    private val tmdbClient = OkHttpClient().newBuilder()
                                .addInterceptor(authInterceptor)
                                .build()


  
    fun retrofit(a) : Retrofit = Retrofit.Builder()
                .client(tmdbClient)
                .baseUrl("https://api.themoviedb.org/3/")
                .addConverterFactory(MoshiConverterFactory.create())
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .build()   

  
   val tmdbApi : TmdbApi = retrofit().create(TmdbApi::class.java)

}
Copy the code

Take a look at what we did in the apifactory.kt file.

  • First, we create a network interceptor named authInterceptor that adds the API_key parameter to all requests.
  • We then create a web client using OkHttp and add authInterceptor.
  • Next, we wire everything together using Retrofit to build the constructor and processor for the Http request. Here we add the network client we created earlier, the base URL, a converter, and an adapter factory. The first is MoshiConverter, which AIDS JSON parsing and converts the response JSON into Kotlin data classes for optional parsing if needed. The second is the CoroutineCallAdaptor, which is of type Retorofit2CallAdapter.FactoryFor processingKotlin coroutines Deferred.
  • Finally, we can create our TmdbApi simply by passing a reference to the TmdbApi class (created in the next section) into the previously created Retrofit class.

To explore the Tmdb API

We call the /movie/popular interface and get the following response. The response returns results, which is an array of Movie objects. That’s what we’re looking at.

{
  "page": 1."total_results": 19848."total_pages": 993."results": [{"vote_count": 2109."id": 297802."video": false."vote_average": 6.9."title": "Aquaman"."popularity": 497.334."poster_path": "/5Kg76ldv7VxeX9YlcQXiowHgdX6.jpg"."original_language": "en"."original_title": "Aquaman"."genre_ids": [
        28.14.878.12]."backdrop_path": "/5A2bMlLfJrAfX9bqAibOL2gCruF.jpg"."adult": false."overview": "Arthur Curry learns that he is the heir to the underwater kingdom of Atlantis, and must step forward to lead his people and be a hero to the world."."release_date": "2018-12-07"
    },
    {
      "vote_count": 625."id": 424783."video": false."vote_average": 6.6."title": "Bumblebee"."popularity": 316.098."poster_path": "/fw02ONlDhrYjTSZV8XO6hhU3ds3.jpg"."original_language": "en"."original_title": "Bumblebee"."genre_ids": [
        28.12.878]."backdrop_path": "/8bZ7guF94ZyCzi7MLHzXz6E5Lv8.jpg"."adult": false."overview": "On the run in the year 1987, Bumblebee finds refuge in a junkyard in a small Californian beach town. Charlie, on the cusp of turning 18 and trying to find her place in the world, discovers Bumblebee, battle-scarred and broken. When Charlie revives him, she quickly learns this is no ordinary yellow VW bug."."release_date": "2018-12-15"}}]Copy the code

So now we can create our Movie data class and MovieResponse class based on this JSON.

// Data Model for TMDB Movie item
data class TmdbMovie(
    val id: Int.val vote_average: Double.val title: String,
    val overview: String,
    val adult: Boolean
)

// Data Model for the Response returned from the TMDB Api
data class TmdbMovieResponse(
    val results: List<TmdbMovie>
)

//A retrofit Network Interface for the Api
interface TmdbApi{
    @GET("movie/popular")
    fun getPopularMovie(a): Deferred<Response<TmdbMovieResponse>>
}
Copy the code

TmdbApi interface:

With the data class created, we create the TmdbApi interface, a reference to which we added to the Retrofit builder in the previous section. In this interface, we add all the required API calls, and you can add any parameters to those calls if necessary. For example, to be able to get a movie by ID, we add the following method to the interface:

interface TmdbApi{

    @GET("movie/popular")
    fun getPopularMovies(a) : Deferred<Response<TmdbMovieResponse>>

    @GET("movie/{id}")      
    fun getMovieById(@Path("id") id:Int): Deferred<Response<Movie>>

}
Copy the code

Finally, the network call is made

We then finally make a request to get the data we need, which we can make either in the DataRepository or in the ViewModel or directly in the Activity.

The sealing the Result class

This is the class used to handle network responses. It may successfully return the required data, or it may fail due to an exception.

sealed class Result<out T: Any> {
    data class Success<out T : Any>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}
Copy the code

Build the BaseRepository that handles safeApiCall calls

open class BaseRepository{

    suspend fun <T : Any> safeApiCall(call: suspend(a) -> Response<T>, errorMessage: String): T? {

        val result : Result<T> = safeApiResult(call,errorMessage)
        var data : T? = null

        when(result) {
            is Result.Success ->
                data = result.data
            is Result.Error -> {
                Log.d("1.DataRepository"."$errorMessage & Exception - ${result.exception}")}}return data

    }

    private suspend fun <T: Any> safeApiResult(call: suspend(a)-> Response<T>, errorMessage: String) : Result<T>{
        val response = call.invoke()
        if(response.isSuccessful) return Result.Success(response.body()!!)

        return Result.Error(IOException("Error Occurred during getting safe Api result, Custom ERROR - $errorMessage"))}}Copy the code

Build MovieRepository

class MovieRepository(private val api : TmdbApi) : BaseRepository() {
  
    fun getPopularMovies(a): MutableList<TmdbMovie>? {//safeApiCall is defined in BaseRepository.kt (https://gist.github.com/navi25/67176730f5595b3f1fb5095062a92f15)
      val movieResponse = safeApiCall(
           call = {api.getPopularMovie().await()},
           errorMessage = "Error Fetching Popular Movies"
      )
      
      return movieResponse?.results.toMutableList();
    
    }

}
Copy the code

Create a ViewModel to get the data

class TmdbViewModel : ViewModel() {private val parentJob = Job()

    private val coroutineContext: CoroutineContext
        get() = parentJob + Dispatchers.Default

    private val scope = CoroutineScope(coroutineContext)

    private val repository : MovieRepository = MovieRepository(ApiFactory.tmdbApi)
    

    val popularMoviesLiveData = MutableLiveData<MutableList<ParentShowList>>()

    fun fetchMovies(a){
        scope.launch {
            val popularMovies = repository.getPopularMovies()
            popularMoviesLiveData.postValue(popularMovies)
        }
    }


    fun cancelAllRequests(a) = coroutineContext.cancel()

}
Copy the code

Update the UI using the ViewModel in your Activity

class MovieActivity : AppCompatActivity() {private lateinit var tmdbViewModel: TmdbViewModel
  
     override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_movie)
       
        tmdbViewModel = ViewModelProviders.of(this).get(TmdbViewModel::class.java)
       
        tmdbViewModel.fetchMovies()
       
        tmdbViewModel.popularMovies.observe(this, Observer {
            
            //TODO - Your Update UI Logic}}})Copy the code

This article is a basic but comprehensive introduction to production-level API calls in Android. For more examples, visit here.

Have fun programming!

If you find any errors in the translation or other areas that need improvement, you are welcome to revise and PR the translation in the Gold Translation program, and you can also get corresponding bonus points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


Diggings translation project is a community for translating quality Internet technical articles from diggings English sharing articles. The content covers the fields of Android, iOS, front end, back end, blockchain, products, design, artificial intelligence and so on. For more high-quality translations, please keep paying attention to The Translation Project, official weibo and zhihu column.