How to Handle RESTful Web Services Like a Pro with Retrofit, OkHttp, Gson, Glide and Coroutines

If you‘re an Android developer, chances are you‘ve had to integrate your app with a backend API at some point. RESTful web services have become the de facto standard for client-server communication in modern applications. They provide a set of conventions and principles for designing networked applications, using HTTP as the underlying protocol.

Implementing REST clients in Android apps used to be a tedious and error-prone task. You had to deal with lots of boilerplate code for opening network connections, constructing HTTP requests, parsing responses, handling errors, and managing background threads. Fortunately, nowadays there are several battle-tested libraries that can greatly simplify working with web services on Android.

In this post, I‘ll show you how to leverage some of the most popular libraries to handle RESTful APIs like a pro: Retrofit for API service definitions, OkHttp for the HTTP client, Gson for JSON serialization, Glide for image loading, and Kotlin Coroutines for asynchronous programming. I‘ll share best practices and code examples you can apply in your own projects. Let‘s get started!

The Power Combo for REST: Retrofit, OkHttp and Gson

The core libraries you‘ll want to add to your networking arsenal are Retrofit, OkHttp and Gson. They work together beautifully to abstract away the low-level details of HTTP communication and allow you to work with a clean, type-safe API.

Retrofit is a REST client library developed by Square. It allows you to define your API as a Java or Kotlin interface with special annotations to describe the HTTP requests. Retrofit will generate an implementation of your API interface that you can use to make network calls.

Here‘s an example of what a Retrofit API service interface looks like:

interface BlogService {  
    @GET("posts")
    suspend fun getPosts(): List<Post>

    @GET("posts/{id}") 
    suspend fun getPost(@Path("id") postId: Long): Post
}

With just a few lines of code, Retrofit allows us to model our API endpoints in a declarative and expressive way. It supports various request types (GET, POST, PUT, DELETE, etc.), URL path and query parameters, request bodies, multipart uploads, and more.

Under the hood, Retrofit uses OkHttp as its HTTP client. OkHttp is a powerful library for sending and receive HTTP-based network requests. It offers features like connection pooling, transparent gzip compression, response caching, and request/response interceptors.

The great thing is that you can customize and extend the behavior of OkHttp to suit your app‘s needs. For example, you can add an interceptor to modify outgoing requests or inspect incoming responses:

val client = OkHttpClient.Builder()  
    .addInterceptor(AuthInterceptor())
    .build()

class AuthInterceptor : Interceptor {  
    override fun intercept(chain: Interceptor.Chain): Response {
        val requestWithAuth = chain.request().newBuilder()
            .addHeader("Authorization", "Bearer $accessToken")
            .build()
        return chain.proceed(requestWithAuth)  
    }
}

This interceptor automatically adds an authorization header with an access token to every outgoing request. Interceptors are a powerful mechanism for cross-cutting concerns like authentication, logging, analytics, etc.

To convert JSON response bodies to Kotlin objects, Retrofit supports a number of converter libraries. The most popular one is Gson, Google‘s JSON serialization/deserialization library. With the GsonConverterFactory, Retrofit can automatically parse JSON responses into your model classes:

data class Post(  
    val id: Long, 
    val title: String,
    val body: String, 
    val userId: Long
)

val retrofit = Retrofit.Builder()  
    .baseUrl("https://jsonplaceholder.typicode.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

Now when we call a method on our API interface, Retrofit will use Gson to deserialize the JSON response body to a Post object.

Effective Data Loading with Network Bound Resource

Having a clean API layer powered by Retrofit is great, but how do we effectively load data in our app? A common pattern is to fetch data from the network, cache it locally in a database, and then display it in the UI. This allows your app to work offline and gives a better user experience.

One way to implement this is using the network bound resource pattern. It goes like this:

  1. Check if data is available in local cache
  2. If not, fetch from network and cache response
  3. If fetch fails, show an error message
  4. If data is in cache, load it from cache and refresh cache in background

Here‘s a simplified version of a network bound resource using Kotlin Coroutines and Android‘s Architecture Components:

class NetworkBoundResource<T>(  
    private val query: () -> LiveData<T>,
    private val fetch: suspend () -> T,
    private val saveFetchResult: (T) -> Unit
) {
    fun asLiveData() = liveData {  
        val data = query().firstOrNull()
        if (data == null) {  
            val response = fetch()
            saveFetchResult(response)  
            emit(Resource.success(response)) 
        }
        else {
            emit(Resource.loading(data))  
            val response = fetch() 
            saveFetchResult(response)
            emit(Resource.success(response))
        } 
    }
}

enum class Status { LOADING, SUCCESS, ERROR }  
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {  
    companion object {
        fun <T> success(data: T?): Resource<T> = Resource(Status.SUCCESS, data, null)
        fun <T> error(msg: String): Resource<T> = Resource(Status.ERROR, null, msg)  
        fun <T> loading(data: T?): Resource<T> = Resource(Status.LOADING, data, null)
    }  
}

The NetworkBoundResource class takes three lambdas as constructor parameters:

  • query defines how to load cached data from the database
  • fetch defines how to fetch fresh data from the network
  • saveFetchResult defines how to cache network response in the database

It exposes the data as a LiveData<Resource<T>>. The Resource class encapsulates both the data and its fetch status (loading, success, error). In the implementation, we first check if cached data is available. If yes, we return it immediately but still trigger a background refresh. If not, we fetch from network, cache the response, and then return it.

To use this, you would define a repository class to fetch a specific type of data:

class PostRepository(  
    private val postDao: PostDao, 
    private val postService: PostService
) {
    fun getPosts(): LiveData<Resource<List<Post>>> {
        return NetworkBoundResource( 
            query = { postDao.getPosts() },
            fetch = { postService.getPosts() }, 
            saveFetchResult = { posts -> postDao.insertPosts(posts) }
        ).asLiveData()
    }
}

The repository implements the high-level data access logic using the network bound resource. It delegates the actual database and network operations to the DAO and Retrofit service respectively. This keeps a clean separation of concerns between layers.

In your ViewModel, you can then expose the data to your UI:

class PostViewModel(postRepository: PostRepository) : ViewModel() {  
    val posts: LiveData<Resource<List<Post>>> = postRepository.getPosts()
}

And finally in your fragment/activity, you observe the LiveData and update the UI accordingly:

viewModel.posts.observe(this) { posts ->  
    when (posts.status) {
        Status.LOADING -> showLoading()  
        Status.SUCCESS -> showPosts(posts.data)
        Status.ERROR -> showError(posts.message)  
    }
}

This pattern provides a clean and efficient way to load data in your app, with support for caching, background updates, and error handling. The use of coroutines makes the asynchronous code very readable and hides the low-level threading details.

Of course this is just the tip of the iceberg – in a real app you‘ll have to deal with paging, invalidation, retries and more. But I hope this gives you a solid starting point for architecting the data layer of your app.

Seamless Image Loading with Glide

In addition to loading data, most apps also need to display images from the network. Loading images can be tricky – you have to take care of background threading, caching, memory management, and more. That‘s where image loading libraries like Glide come in.

Glide is a fast and efficient open source media management and image loading framework for Android. It wraps image fetching, decoding, memory/disk caching and resource pooling into a simple and easy to use interface.

Here‘s how you can load an image into an ImageView with Glide:

GlideApp.with(context)  
    .load(imageUrl)
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)  
    .fallback(R.drawable.fallback)
    .transform(CircleCrop())
    .into(imageView)

With a single line of code, Glide will asynchronously load the image from the given URL, display a placeholder while loading, handle errors, apply transformations, and cache the result. It automatically manages the memory and disk caches, and recycles resources when possible.

Glide has built-in support for common image formats like JPEG, GIF, PNG and WebP. It can load images from various sources such as network, local storage, resources, assets, URI, and more.

A lesser known but very useful feature of Glide is the ability to integrate with a custom networking stack. If your app uses OkHttp for HTTP requests, you can configure Glide to use the same OkHttpClient instance:

val okHttpClient: OkHttpClient = OkHttpClient.Builder()  
    .addInterceptor(AuthInterceptor())
    .build()

val glide = GlideApp.get(context)  
glide.registry.replace(GlideUrl::class.java, InputStream::class.java,  
    OkHttpUrlLoader.Factory(okHttpClient))

This way, Glide will use your custom configured OkHttp client for image requests, complete with your interceptors, connection pool, caching, etc. This can be very useful for authentication, analytics, or other custom logic.

Another neat trick is using Glide‘s generated API for compile-time safety and a fluent interface. To do this, you need to include an AppGlideModule implementation in your app:

@GlideModule  
class MyAppGlideModule : AppGlideModule() {  
    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        registry.replace(GlideUrl::class.java, InputStream::class.java,  
            OkHttpUrlLoader.Factory(okHttpClient))    
    }
}

This will generate a GlideApp class with generated GlideRequests and GlideRequest classes that extend Glide‘s API. You can then use the GlideApp class to access the generated APIs and add your own extension methods.

Wrapping Up

We‘ve covered a lot of ground in this post – from Retrofit and OkHttp for making API requests, to Gson for JSON parsing, to network bound resources for efficient data loading, to Glide for seamless image loading. These libraries provide a solid foundation for building robust and performant Android apps that communicate with a backend API.

But as with all technology choices, it‘s important to evaluate your specific needs and constraints. Retrofit and OkHttp are great for most use cases, but if you need something more lightweight you might consider alternatives like Fuel or Ktor. For image loading, Picasso and Coil are also popular options.

Remember that these libraries are just tools – it‘s up to you to use them effectively in the context of your app architecture. A clean separation of concerns, modular design, and proper testing will go a long way to ensuring your app is maintainable and scalable.

I encourage you to dive deeper into the documentation of these libraries and experiment with them in your own projects. And if you have any tips or best practices to share, feel free to reach out – I‘m always eager to learn from the Android community.

Happy coding!

Similar Posts