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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android Networking With Kotlin Tutorial: Getting Started
30 mins
- Getting Started
- Enabling Auto Import
- Including Permissions
- Making a Network Request
- Initiating a Request on a Background Thread
- Reading the URL Content
- Checking the Network Availability
- Confirming Network Connection
- Using a Network Connectivity Helper Method
- Updating the UI
- Defining a Data Model
- Updating the RecyclerView Adapter
- Adding the GSON Library
- Updating the Network Call
- Lengthening Your Search Result
- Updating Your App With Retrofit
- Introducing Retrofit
- Using Retrofit Service to Make Network Calls
- Using Retrofit to Handle Requests and Responses
- Using Retrofit With Coroutines
- Simplifying Retrofit With Coroutines
- Making the Network Call With Coroutines
- Network Profiling
- Using the Network Profiler
- Exploring Network Profiler
- Adding Images
- Where to Go From Here?
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
:
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:
- Specifies the base URL.
- Creates a Retrofit object.
- Specifies
GsonConverterFactory
as the converter, which uses Gson for its JSON deserialization. - Generates an implementation of the GithubService interface using the Retrofit object.
- Has a method to create a Retrofit
Call
, on whichenqueue()
makes a network call and passes in a Retrofit callback. A successful response body type is set toRepoResult
.
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:
- A
RepositoryRetriever
object. - A Retrofit
Callback
that has two overrides:onFailure()
andonResponse()
.
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:
- You create a coroutine scope by using a job to cancel when needed.
- Then you create
CoroutineExceptionHandler
to handle any exceptions. - 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.
- 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.