Android Networking With Kotlin Tutorial: Getting Started

In this tutorial, you’ll get started with Android networking by creating a simple app to retrieve and display a list of GitHub repositories. By Fuad Kamal.

3.9 (20) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Using Retrofit Service to Make Network Calls

Now, you’ll create a new package in your app called api by right-clicking on the root package and picking New ▸ Package.

Right-click on api. From the context menu, select New ▸ Kotlin File/Class. Give it the name GithubService, and select Interface for Kind:

new file

Replace the contents of GithubService.kt below the package statement with the following:

import com.raywenderlich.android.githubrepolist.data.RepoResult
import retrofit2.Call
import retrofit2.http.GET

interface GithubService {
  @GET("/repositories")
  fun retrieveRepositories(): Call<RepoResult>

  @GET("/search/repositories?q=language:kotlin&sort=stars&order=desc&per_page=50") 
//sample search
  fun searchRepositories(): Call<RepoResult>
}

This code creates an interface that lets Retrofit connect to the GitHub API. You’ve also added two methods to the interface with @GET annotations that specify the GitHub endpoints to make GET requests.

Make a second file in api. Select Class for Kind, and name it RepositoryRetriever. Replace the empty class with the following:

// Other imported classes
import retrofit2.Callback

class RepositoryRetriever {
  private val service: GithubService

  companion object {
    //1
    const val BASE_URL = "https://api.github.com/"  
  }

  init {
    // 2
    val retrofit = Retrofit.Builder()
         // 1
        .baseUrl(BASE_URL) 
         //3
        .addConverterFactory(GsonConverterFactory.create()) 
        .build()
    //4
    service = retrofit.create(GithubService::class.java) 
  }

  fun getRepositories(callback: Callback<RepoResult>) { //5
    val call = service.searchRepositories()
    call.enqueue(callback)
  }
}

Here’s what RepositoryRetriever does:

  1. Specifies the base URL.
  2. Creates a Retrofit object.
  3. Specifies GsonConverterFactory as the converter, which uses Gson for its JSON deserialization.
  4. Generates an implementation of the GithubService interface using the Retrofit object.
  5. Has a method to create a Retrofit Call, on which enqueue() makes a network call and passes in a Retrofit callback. A successful response body type is set to RepoResult.

The Retrofit enqueue() will perform your network call off the main thread.

Using Retrofit to Handle Requests and Responses

Finally, you need to modify MainActivity.kt to use Retrofit to make the network request and handle the response.

First, add the following to properties at the top of MainActivity.kt:

  // 1
  private val repoRetriever = RepositoryRetriever()

  //2
  private val callback = object : Callback<RepoResult> {
    override fun onFailure(call: Call<RepoResult>?, t:Throwable?) {
      Log.e("MainActivity", "Problem calling Github API {${t?.message}}")
    }
    
    override fun onResponse(call: Call<RepoResult>?, response: Response<RepoResult>?) {
      response?.isSuccessful.let {
        val resultList = RepoResult(response?.body()?.items ?: emptyList())
        repoList.adapter = RepoListAdapter(resultList)
      }
    }
  }

Your two properties are:

  1. A RepositoryRetriever object.
  2. A Retrofit Callback that has two overrides: onFailure() and onResponse().

In the successful callback method, you update the RecyclerView adapter with the items in the response.

Update onCreate() to delete doAsync{…} in isNetworkConnected. Replace it with a call to RepositoryRetriever:

override fun onCreate(savedInstanceState: Bundle?) {
  // Switch to AppTheme for displaying the activity
  setTheme(R.style.AppTheme)

  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_main)

  repoList.layoutManager = LinearLayoutManager(this)

  if (isNetworkConnected()) {
    repoRetriever.getRepositories(callback)
  } else {
    AlertDialog.Builder(this).setTitle("No Internet Connection")
        .setMessage("Please check your internet connection and try again")
        .setPositiveButton(android.R.string.ok) { _, _ -> }
        .setIcon(android.R.drawable.ic_dialog_alert).show()
  }
}

If Android Studio has trouble generating the imports, add the following three imports to the class:

import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

Build and run to verify everything works. Your app should look the same, but now Retrofit handles networking under the hood.

Using Retrofit With Coroutines

Up to this point, you’ve used the old way of making network calls with Retrofit: callbacks. But the scale and code of the app can grow more complex. This can lead to “callback hell” – when there are multiple API calls and callbacks are wrapped inside callbacks which can be very difficult to maintain.

Thankfully, Retrofit has built-in support for Kotlin’s coroutines.

Simplifying Retrofit With Coroutines

Time to simplify your Retrofit API service. To do so, you’ll replace the callback and interfaces with coroutines and exception handling.

Open app build.gradle and add the following to the dependencies section:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"

Open GithubService.kt and replace the content of the interface with the following:

    @GET("/repositories")
    suspend fun retrieveRepositories(): RepoResult
    
    //sample search 
    @GET("/search/repositories?q=language:kotlin&sort=stars&order=desc&per_page=50") 
    suspend fun searchRepositories(): RepoResult

Notice that suspend appears in front of the function names. Also, there’s no need to wrap the return value in Call anymore. This transforms the functions into coroutines. So simple!

Next, open RepositoryRetriever.kt. You’ve modified GithubService to use Kotlin coroutines, so you now need to mark getRepositories with suspend. Add suspend before this function to fix the error. However, you won’t need any of this callbacks anymore! Just simplify the function as follows:

suspend fun getRepositories(): RepoResult  {
  return service.searchRepositories()
}

Notice that coroutines need to be run within a specific scope. This is part of what makes them so powerful and thread-safe.

Actually, that’s an oversimplification. If you want to dig into the technical details, see the official documentation regarding thread safety and coroutines.

In more complex apps, you may want to use Android Architecture components and scope your coroutines within ViewModel. But this tutorial will keep things simple, so you’ll leave the network call within the Activity and its lifecycle scope.

Making the Network Call With Coroutines

Open MainActivity.kt and replace the entire class with the following:

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    // Switch to AppTheme for displaying the activity
    setTheme(R.style.AppTheme)

    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    repoList.layoutManager = LinearLayoutManager(this)

    if (isNetworkConnected()) {
      retrieveRepositories()
    } else {
      AlertDialog.Builder(this).setTitle("No Internet Connection")
          .setMessage("Please check your internet connection and try again")
          .setPositiveButton(android.R.string.ok) { _, _ -> }
          .setIcon(android.R.drawable.ic_dialog_alert).show()
    }
  }

  fun retrieveRepositories() {
    //1 Create a Coroutine scope using a job to be able to cancel when needed
    val mainActivityJob = Job()

    //2 Handle exceptions if any
    val errorHandler = CoroutineExceptionHandler { _, exception ->
      AlertDialog.Builder(this).setTitle("Error")
          .setMessage(exception.message)
          .setPositiveButton(android.R.string.ok) { _, _ -> }
          .setIcon(android.R.drawable.ic_dialog_alert).show()
    }

    //3 the Coroutine runs using the Main (UI) dispatcher
    val coroutineScope = CoroutineScope(mainActivityJob + Dispatchers.Main)
    coroutineScope.launch(errorHandler) {
      //4
      val resultList = RepositoryRetriever().getRepositories()
      repoList.adapter = RepoListAdapter(resultList)
    }

  private fun isNetworkConnected(): Boolean {
    val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val activeNetwork = connectivityManager.activeNetwork
    val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork)
    return networkCapabilities != null &&
            networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
  }
}

Here what’s going on in that code:

  1. You create a coroutine scope by using a job to cancel when needed.
  2. Then you create CoroutineExceptionHandler to handle any exceptions.
  3. The coroutine runs using the Main (UI) dispatcher. For a better understanding of how coroutines handle background threading, see the “Where to Go From Here?” section.
  4. The coroutine execution is called.

Step 4 demonstrates the beauty of coroutines. You write them like synchronous code even though they’re asynchronous, making them easier to read and understand!

Now build and run the app. It should work exactly as before. The only difference being that Kotlin coroutines simplify the code and optimize the performance.