Vincent Tsen
Android Kotlin Weekly

Android Kotlin Weekly

Simple REST API Android App in Kotlin - Various HTTP Client Library Implementations

How to use Retrofit, Moshi, Gson, Kotlin Serialization and Ktor client libraries to connect the REST API web service in Android app?

Vincent Tsen's photo
Vincent Tsen
ยทMay 13, 2022ยท

6 min read

Simple REST API Android App in Kotlin - Various HTTP Client Library Implementations

Subscribe to my newsletter and never miss my upcoming articles

Table of contents

I created this simple Android App (written in Kotlin and Jetpack Compose) to help me to understand different ways to connect to REST API web service using different HTTP Client libraries.

I also tried to measure the memory and performance of using these libraries.

Simple_REST_API_Android_App_in_Kotlin_01.gif

This is the REST API - Meals Categories from TheMealDB the app tries to retrieve. It returns in JSON format.

The app is implemented with MVVM, but the following only highlights the steps you need to do to build these HTTP client libraries.

If you want to know the details, please refer to the source code provided at the end of this article.

1. Retrofit + Moshi

This is the first method I learned while creating this Asteroid Rader App in one of my Android Kotlin Developer Nanodegree Projects.

  • Retrofit is the HTTP client library to connect to REST API web service
  • Moshi is the library to parse JSON response into Kotlin data object

Import Retrofit + Moshi Converter Libraries

def retrofit_version = "2.9.0"  
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"  
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"

def moshi_version = "1.12.0"  
implementation "com.squareup.moshi:moshi:$moshi_version"  
implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"

Create Data Class for Moshi

data class MoshiMealCategoriesResponse (
    val categories: List<MoshiMealCategory>
)

data class MoshiMealCategory(
    @Json(name="idCategory") val idCategory: String,
    val strCategory: String,
    val strCategoryDescription: String,
    val strCategoryThumb: String
)

@Json is Moshi annotation and only is needed if your val name is different from the JSON string.

Define Retrofit + Moshi API Interface

interface RetrofitMoshiMealsApi {
    @GET("categories.php")
    suspend fun getMealCategories(): MoshiMealCategoriesResponse
}

Build Retrofit + Moshi API

class RetrofitMoshiMealsWebService {

    private val api: RetrofitMoshiMealsApi by lazy {
        createMealsApi()
    }

    suspend fun getMealCategories(): MoshiMealCategoriesResponse {
        return api.getMealCategories()
    }

    private fun createMealsApi(): RetrofitMoshiMealsApi {
        val moshi = Moshi.Builder()
            .add(KotlinJsonAdapterFactory())
            .build()

        val retrofit = Retrofit.Builder()
            .baseUrl(MainRepository.BASE_URL)
            .addConverterFactory(MoshiConverterFactory.create(moshi))
            .build()

        return retrofit.create(RetrofitMoshiMealsApi::class.java)
    }
}

2. Retrofit + Gson

Similar to Moshi, Gson is an open-source Java library to serialize and deserialize JSON to Kotlin data objects.

Since Moshi import has already shown above, I'm not going to show here again.

Import Gson Converter Library

implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

Create Data Class for Gson

data class GsonMealCategoriesResponse (
    val categories: List<GsonMealCategory>
)

data class GsonMealCategory(
    @SerializedName("idCategory") val idCategory: String,
    val strCategory: String,
    val strCategoryDescription: String,
    val strCategoryThumb: String
)

@SerializedName is Gson annotation, which is similar to @Json in Moshi annotation if your JSON string is different from the val name.

Technically I can share the same data class for all these different JSON parser implementation, but I think it is cleaner to separate as it requires different annotations for different parser libraries.

Define Retrofit + Gson API Interface

interface RetrofitGsonMealsApi {
    @GET("categories.php")
    suspend fun getMealCategories(): GsonMealCategoriesResponse
}

Build Retrofit + Gson API

class RetrofitGsonMealsWebService {  

    private val api: RetrofitGsonMealsApi by lazy {  
        createMealsApi()  
    }  

    suspend fun getMealCategories(): GsonMealCategoriesResponse {  
        return api.getMealCategories()  
    }  

    private fun createMealsApi(): RetrofitGsonMealsApi {  

        val gsonConverterFactory = GsonConverterFactory.create()  

        val retrofit = Retrofit.Builder()  
            .baseUrl(MainRepository.BASE_URL)  
            .addConverterFactory(gsonConverterFactory)  
            .build()  

        return retrofit.create(RetrofitGsonMealsApi::class.java)  
    }  
}

3. Retrofit + Kotlin Serialization

Similar to Moshi and Gson, Kotlin Serialization is an official Kotlin library which can be used to serialize and deserialize JSON to Kotlin data objects.

One of the recommendations I got in the Android Kotlin Developer NonoDegree is to use Kotlin Serialization. The memory and performance are better because it doesn't use reflection.

Add Kotlin Serialization Plugin

Add this in app\build.gradle

plugins {  
  ...
  import project.  id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"  
}

Make sure you update the build.gradle at the app level and not at the project level. If you see this warning below, it is likely you update the wrong build.gradle file (i.e. project level).

Warning:(5, 1) kotlinx.serialization compiler plugin is not applied to the module, so this annotation would not be processed. Make sure that you've setup your buildscript correctly and re-import project.

It took me a while to figure out I updated wrong build.gradle. It compiled fine but failed at run time. Don't make the same mistake I did.

Import Kotlin Serialization Library

implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"

Import Kotlin Serialization Converter + okhttp3 Libraries

There is no official Kotlin Serialization Converter for Retrofit from squareup, and we're using the one from Jake Wharton.

implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"  
implementation "com.squareup.okhttp3:okhttp:4.9.3"

okhttp3 is required for "application/json".toMediaType() usage. See Build Retrofit + Kotlin Serialization section below.

Create Data Class for Kotlin Serialization

@Serializable  
data class KotlinSerdesMealCategoriesResponse (  
    val categories: List<KotlinSerdesMealCategory>  
)

@Serializable  
data class KotlinSerdesMealCategory(  
    @SerialName("idCategory") 
    val idCategory: String,  
    val strCategory: String,  
    val strCategoryDescription: String,  
    val strCategoryThumb: String  
)

Similar to @Json (Moshi) and @SerializedName (Gson), @SerialName is used for Kotlin Serialization. Please note that you need to annotate the class with @Serializable in order to use the Kotlin Serialization.

Define Retrofit + Kotlin Serialization API Interface

interface RetrofitKotlinSerdesMealsApi {  
    @GET("categories.php")  
    suspend fun getMealCategories(): KotlinSerdesMealCategoriesResponse  
}

Build Retrofit + Kotlin Serialization

class RetrofitKotlinSerdesMealsWebService  {  

    private val api: RetrofitKotlinSerdesMealsApi by lazy {  
        createMealsApi()  
    }  

    suspend fun getMealCategories(): KotlinSerdesMealCategoriesResponse {  
        return api.getMealCategories()  
    }  

    @OptIn(ExperimentalSerializationApi::class)  
    private fun createMealsApi(): RetrofitKotlinSerdesMealsApi {  

        val contentType = "application/json".toMediaType()  
        val retrofit = Retrofit.Builder()  
            .baseUrl(MainRepository.BASE_URL)  
            .addConverterFactory(Json.asConverterFactory(contentType))  
            .build()  

        return retrofit.create(RetrofitKotlinSerdesMealsApi::class.java)  
    }  
}

Please note that you need to add @OptIn(ExperimentalSerializationApi::class) in order to use the converter library. You also need to add the opt-in compiler argument in your build.gradle module level file.

android {

    ...

    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
        kotlinOptions {
            freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
        }
    }
}

You can also refer to the following blog post:

4. Ktor Client + Kotlin Serialization

Ktor client is a multiplatform HTTP client library.

Import Ktor Client with Kotlin Serialization Libraries

def ktor_version = "1.6.8"  
implementation "io.ktor:ktor-client-core:$ktor_version"  
implementation "io.ktor:ktor-client-cio:$ktor_version"  
implementation "io.ktor:ktor-client-serialization:$ktor_version"

Create Ktor Client and Implement API Interface

The data class is exactly the same with the Kotlin Serialization data class above . So I'm not going to show here again.

To use Ktor Client, we don't really need to define the interface as required by Moshi. You can just call ktorHttpClient.get("URL here") API directly.

class KtorKotlinSerdesMealsWebService {  

    private val ktorHttpClient = HttpClient {  
        install(JsonFeature) {  
        serializer = KotlinxSerializer()  
    }  
 }

 suspend fun getMealCategories(): KotlinSerdesMealCategoriesResponse {  
        return ktorHttpClient.get("${MainRepository.BASE_URL}categories.php")  
    }  
}

Memory and Performance

I added this Enable Performance Test check box to the main screen. When it is checked, it will call the API 10 times for performance testing.

Simple_REST_API_Android_App_in_Kotlin_02.png

I ran some memory and performance tests and here are the results. I ran a couple of times and took the average. I also restarted the app to run HTTLP client library independently, so the results won't be overlapped.

HTTP Client LibraryMemory UsagePerformance
Retrofit + Moshi22 M bytes4.8 seconds
Retrofit + Gson19 M bytes4.9 seconds
Retrofit + Kotlin Serialization20 M bytes4.9 seconds
Ktor Client + Kotlin Serialization22 M bytes10.8 seconds

Memory and performance for Retrofit + Gson and Retrofit + Kotlin Serialization are similar. Retofit + Moshi uses slightly more memory with similar performance, but it could be just false positive.

But, what happen to Ktor Cilent? Ktor Client's performance is around 2x slower!

Conclusion

Before I ran the memory and performance, I had an impression Ktor Client + Kotlin Serialization must be the best option, but it turned out to be worst! Maybe it is because of multiplatform overhead?

Also, the claim for Kotlin Serialization use less memory and faster is probably not true. It is about the same as Moshi and Gson or I did not run the test right?

It is very obvious the choice is Retrofit.

Personally, I will probably choose

  • Moshi over Gson because Moshi is a newer library than Gson
  • Moshi over Kotlin Serialization because Retrofit Kotlin Serialization Converter is NOT an official library(not part of the squareup libraries).

Given this little research that I have done, my go-to is Retrofit + Moshi.

Source Code

GitHub Repository: Demo_SimpleRestAPI

Did you find this article valuable?

Support Vincent Tsen by becoming a sponsor. Any amount is appreciated!

See recent sponsors |ย Learn more about Hashnode Sponsors
ย 
Share this