Android Networking: Beyond the Basics

Sep 8 2022 · Kotlin 1.7.10, Android 12, Android Studio Chipmunk

Part 2: Retrofit With Kotlin Coroutines

11. Challenge: Coroutines

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 10. Include Built-in Retrofit Support for Coroutines Next episode: 12. Conclusion

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 11. Challenge: Coroutines

Since you’re now a networking pro, there’s not much else to do but practice using Kotlin Coroutines, for other requests. In this challenge, all you have to do is change the getTasks, completeTask, addTask and getUserProfile calls, to use the built-in support from Retrofit.

You can use the deleteTask call as a reference, for other calls! :] That’s it! Now pause the video, and solve the challenge! Then once you’re done, unpause the video, and compare the two solutions! Good luck! :]

Demo

Start by opening the RemoteApiService.kt file. Change the following calls to be suspendable, and remove the call generic return type:

@GET("/api/note")
suspend fun getNotes(): GetTasksResponse

@GET("/api/user/profile")
suspend fun getMyProfile(): UserProfileResponse

@POST("/api/note/complete")
suspend fun completeTask(@Query("id") noteId: String): CompleteNoteResponse

@POST("/api/note")
suspend fun addTask(@Body request: AddTaskRequest): Task

Here you’re just modifying calls to rely on the built-in support. Head over to the RemoteApi class, and change the code as follows. You can use the Refactor -> Change Signature option to have an easier time refactoring the code, but you have to be careful not to forget any functionality.

suspend fun getTasks(): Result<List<Task>> = try {
  val data = apiService.getNotes().notes

  Success(data.filter { !it.isCompleted })
} catch (error: Throwable) {
  Failure(error)
}

suspend fun completeTask(taskId: String): Result<String> = try {
  val response = apiService.completeTask(taskId)

  Success(response.message!!)
} catch (error: Throwable) {
  Failure(error)
}

suspend fun addTask(addTaskRequest: AddTaskRequest): Result<Task> = try {
  val task = apiService.addTask(addTaskRequest)

  Success(task)
} catch (error: Throwable) {
  Failure(error)
}

suspend fun getUserProfile(): Result<UserProfile> = try {
  val notesResult = getTasks()

  if (notesResult is Failure) {
    Failure(notesResult.error)
  } else {
    val notes = notesResult as Success
    val data = apiService.getMyProfile()

    if (data.name == null || data.email == null) {
      Failure(NullPointerException("No data available!"))
    } else {
      Success(UserProfile(data.email, data.name, notes.data.size))
    }
  }
} catch (error: Throwable) {
  Failure(error)
}

There are a lot of changes here, but they are all the same. Removing callbacks, and returning a try/catch block expression, with a result as the return value. By now, you should be familiar with this structure! Finally, head over to each call site and change the code to utilize the new result, and avoid callbacks:

viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
  val result = remoteApi.getUserProfile()
  withContext(Dispatchers.Main) {
    if (result is Success) {
...
}      

viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
  val result = remoteApi.getTasks()
  withContext(Dispatchers.Main) {
    if (result is Success) {
...
}

viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
  val result = remoteApi.completeTask(taskId)

  withContext(Dispatchers.Main) {
    if (result is Success) {
...
}

viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
  val result = remoteApi.addTask(AddTaskRequest(title, content, priority))
  withContext(Dispatchers.Main) {
    if (result is Success) {
...
}

You can see a pattern here. Not only is it easy to refactor the code, but every single call is now more understandable, and it looks like regular, blocking code, except that it’s performant and handles threading inherently. Run the project, and play with the API calls.

Everything should work as before, but now the business logic call is much simpler than you originally implemented it, with HTTP connections, and Retrofit callbacks! :]