Getting Started with Retrofit using Kotlin and RxJava - Android Networking Library

Networking is one of the primary components of any Android application. There are a lot of good libraries out there that helps in making networking fairly easy for us. A few examples of such libraries include Retrofit and FastAndroidNetworking.

In this article, I am going to provide you the essential setup and concepts required to build an Android application using Retrofit with RxJava and Kotlin

A brief introduction to Retrofit

Retrofit is a rest client that uses OkHttp as the HttpClient and Json parser to parse the response. OkHttp has become the defacto Http client in almost all Android applications. Retrofit basically provides a better wrapper around the OkHttp.

Simple steps to setup Retrofit:

object Networking {

    private const val NETWORK_CALL_TIMEOUT = 60

    fun create(baseUrl: String, cacheDir: File, cacheSize: Long): NetworkService {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(
                OkHttpClient.Builder()
                    .cache(Cache(cacheDir, cacheSize))
                    .addInterceptor(HttpLoggingInterceptor()
                        .apply {
                            level = if (BuildConfig.DEBUG) 
                                HttpLoggingInterceptor.Level.BODY
                            else 
                                HttpLoggingInterceptor.Level.NONE
                        })
                    .readTimeout(NETWORK_CALL_TIMEOUT.toLong(), TimeUnit.SECONDS)
                    .writeTimeout(NETWORK_CALL_TIMEOUT.toLong(), TimeUnit.SECONDS)
                    .build()
            )
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
            .create(NetworkService::class.java)
    }
}

You can create this an instance of the NetworkService like this:

Networking.create(
    "https://example.mindorks.com/",
    application.cacheDir,
    10 * 1024 * 1024 // 10MB
)

Did you get what is NetworkService?

You must not have as I have not explained it yet. Wait for some more time. Before that let's try to understand various instances provided in the configuration to create NetworkService (which is a ghost till here).

  • BaseUrl: This is the string which defines the URL part that is common to all APIs. Example:

https://example.mindorks.com/get-all-users

Here: https://example.mindorks.com/ is the base url and get-all-users is the endpoint.

  • OkHttpClient adds all the functionality of networking to the Retrofit. You need to define few things in its builder if you want to explicitly customize them, else most of these things will fall back to the default values.
  • Cache: Use this to enable and set the offline caching ability for OkHttp.

You can learn more on HTTP caching in this video lecture

  • HttpLoggingInterceptor: This is an interceptor which will log the requests and responses of your APIs in the logcat for the debug build.

You can learn more about Interceptor in this video lecture:

  • Timeouts: It provides the Http connection time limits for reading from the server and writing on the client.

You can learn more about OkHttp History and Timeouts in this video lecture:

  • GsonConvertorFactory: You need to specify which library Retrofit should be used for Serialization and Deserialization. Gson is used here, which is a popular library that helps in Object to JSON and JSON to Object conversions.
  • RxJava2CallAdapterFactory: Retrofit by default provides Call object for getting the HTTP response. But in our case, we need that our response should be observed in an Observable or Single as in our code example. So, to achieve this we need to tell Retrofit to use RxJava instead of its default Call wrapper.
  • NetworkService: After you build the Retrofit instance with the above configuration, you need to pass the interface that defines the API signatures that Retrofit need to handle. We will explore the NetworkService now.
interface NetworkService {

    /*---------------------HTTP METHODS-----------------------------*/
    // makes http get request with header
    @GET("dummy/list") // endpoint will be appended to the base url
    fun doDummyGetRequestCall(
        @Header("some-header-key") someHeader: String
    ): Single<DummyResponse>

    // makes http post request with body and header
    @POST("dummy/list")
    fun doDummyPostRequestCall(
        @Body request: DummyRequest,
        @Header("some-header-key") someHeader: String,
        @Header("some-more-header-key") someMoreHeader: String
    ): Single<DummyResponse>

    // makes http put request with body and header
    @PUT("dummy/list")
    fun doDummyPutRequestCall(
        @Body request: DummyRequest,
        @Header("some-header-key") someHeader: String
    ): Single<DummyResponse>

    // makes http delete request with body and header
    @DELETE("dummy/list")
    fun doDummyDeleteRequestCall(
        @Header("some-header-key") someHeader: String,
        @Header("some-more-header-key") someMoreHeader: String
    ): Single<DummyResponse>

    /*----------------------ADDITIONAL THINGS---------------------------*/
    
    // makes http get request with dynamic endpoint and header
    @GET("dummy/{userId}/list")
    fun doDummyGetRequestVariablePathCall(
        @Path("userId") userId: String,
        @Header("some-header-key") someHeader: String
    ): Single<DummyResponse>

    // makes http get request with query parameter and header
    // base url + dummy/list/search?query=<some query value>
    @GET("dummy/list/search")
    fun doDummyGetRequestQueryParamsCall(
        @Query("query") query: String,
        @Header("some-header-key") someHeader: String
    ): Single<DummyResponse>
}

Request and Response classes are provided below:

data class DummyRequest(
    @Expose
    @SerializedName("id")
    var id: String
)

data class DummyResponse(
    
    @Expose
    @SerializedName("message")
    var message: String,

    @Expose
    @SerializedName("data")
    val data: List<Dummy>
)

data class Dummy(
    @Expose
    @SerializedName("name")
    val name: String,

    @Expose
    @SerializedName("imageUrl")
    val imageUrl: String?
)

  • Multipart: You can upload multiple files and images in a single request using the multipart.
@Singleton
interface NetworkService {

    ...

    @Multipart
    @POST("user/profile-pic")
    fun doImageUploadCall(
        @Part image: MultipartBody.Part,
        @Header("some-header-key") someHeader: String
    ): Single<ImageUploadResponse>
}

data class ImageUploadResponse(

    @Expose
    @SerializedName("message")
    val message: String,

    @Expose
    @SerializedName("imageUrl")
    val imageUrl: String
)

fun makeImageUpload(image: File): Single<String> {
    val filePart = MultipartBody.Part.createFormData(
        "image",
        image.name,
        RequestBody.create(MediaType.parse("image/*"), image))
    return networkService.doImageUploadCall(filePart).map { /*Do Somthing*/ }
}

You can know more about the Multipart concept in this video lecture:

You can also watch the below video lecture that describes some of the concepts of Retrofit discuss in this article.

This ends this article. I hope you must have got the concepts of handling networking in an Android application.