Android Networking: Beyond the Basics

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

Part 1: Implement Advanced Retrofit

03. Implement Logging Interceptors & Error Handling

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: 02. Use Different Parsers Next episode: 04. Challenge: Error Handling

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: 03. Implement Logging Interceptors & Error Handling

When dealing with requests, you can always use more information to help you either see where the problem lies in case you receive an error, or fail to parse some data.

And you can always use more data when the request is finished, but you have an error response from the server, as error handling can be pretty complicated sometimes.

Dealing with try/catch blocks, checking exception types, using the status code for more information, and finding ways to communicate that to us, developers, is very important, and because of this, you need to be able to log request information and handle errors with ease.

You’ll implement a logging interceptor. A tool that will take every request and response, and log its data to the console, for you to be able to analyze!

After that, you’ll top it off by adding some customized error handling, to provide a more clear way of understanding if a request was a success or a failure. Let’s see how to do this! :]

Demo

To add the logging interceptor, first, open the app level build.gradle, and add the following dependency:

implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'

Sync the project. The OkHttp logging interceptor is a class you’ll use to log the body, the headers, and endpoint paths to the console, for analysis.

Head over to the RetrofitBuilders.kt file, and add the following code to the OkHttpClient.

.addInterceptor(HttpLoggingInterceptor().apply {
  level = HttpLoggingInterceptor.Level.BODY
})

By adding this interceptor, you mentioned you want to log the BODY level of the request, which basically means a very detailed report of the sent and received data, as well as headers and some basic parameters. Run the project, and open Logcat. In the search, write down HTTP, and you should see a lot of data being logged, with each request!

You can always disable such detailed logging, for release versions of the app, for security reasons, and only keep the logs in Debug mode. You can see things like the headers, the REST method, the start and end of requests, the JSON body, and much more.

Now, for the second part, you’ll implement a more user-friendly way to handler errors. Create a new class called Result, in the model package. Add the following code to the class:

sealed class Result<out T : Any>

data class Success<out T : Any>(val data: T) : Result<T>()

data class Failure(val error: Throwable?) : Result<Nothing>()

Using sealed classes in Kotlin, you can create a type that only has a fixed set of subtypes. In this case, you have a generic Result type, out of Any type, which can be either a Success class, with a data property of some type, like a String. And a Failure class, which is a Result out of nothing, and holds an error instead. You’ll see how this fits into cleaner error handling in a bit.

Now open the RemoteApi.kt file, and head over to the loginUser call. Change the lambda type, to take in a Result of the String type, instead of the two parameters it takes in now:

fun loginUser(userDataRequest: UserDataRequest, onUserLoggedIn: (Result<String>) -> Unit)

Make sure to import your own Result, as Kotlin also has a Result class in the standard library. Instead of taking in both a string and a throwable, which are nullable, you always send back a result. But the result can be a Success or Failure case, with different data within. Change the code for the lambda invocation:

onUserLoggedIn(Failure(error))

...

if (loginResponse == null || loginResponse.token.isNullOrEmpty()) {
  onUserLoggedIn(Failure(NullPointerException("No response body!")))
} else {
  onUserLoggedIn(Success(loginResponse.token))
}

Instead of returning the data raw, you wrap it in a result! :] This way you always get a result back. Head over to the LoginActivity now, and let’s change the way the result is processed:

remoteApi.loginUser(userDataRequest) { result ->
  if (result is Success) {
    onLoginSuccess(result.data)
  } else {
    showLoginError()
  }
}

This is much simpler to understand than the previous checks for nullability. Now you know that if there is a Success result, you can proceed with the happy path, and if there is a failure, you have an error and can proceed with the negative or unhappy path.

Notice how the result here is smart cast to Success, and the data is available. This is because the compiler knows that after an is check, it is bound to be that type. Now run the project, and check that logging in still works! :]