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

vtsen

Vincent Tsen

Posted on June 3, 2022

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 services in Android app?

I created this simple Android App (written in Kotlin 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 that 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 the REST API web service
  • Moshi is the library to parse JSON responses 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"
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

@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
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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 been shown above, I'm not going to show it here again.

Import Gson Converter Library

implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

@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
}
Enter fullscreen mode Exit fullscreen mode

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)  
    }  
}
Enter fullscreen mode Exit fullscreen mode

3. Retrofit + Kotlin Serialization

Similar to Moshi and Gson, Kotlin Serialization is an official Kotlin library that 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"  
}
Enter fullscreen mode Exit fullscreen mode

Make sure you update the build.gradle at the app level and not at the project level. If you see this warning below, you likely 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.
Enter fullscreen mode Exit fullscreen mode

It took me a while to figure out I updated the 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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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  
)
Enter fullscreen mode Exit fullscreen mode

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  
}
Enter fullscreen mode Exit fullscreen mode

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)  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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"
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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")  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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 Library Memory Usage Performance
Retrofit + Moshi 22 M bytes 4.8 seconds
Retrofit + Gson 19 M bytes 4.9 seconds
Retrofit + Kotlin Serialization 20 M bytes 4.9 seconds
Ktor Client + Kotlin Serialization 22 M bytes 10.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 a false positive.

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

Note: The performance results above may not be updated. I didn't rerun it after I have upgraded the example to the latest library versions. So you may want to run it yourself to confirm what I claimed is still true.

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 the worst! Maybe it is because of multiplatform overhead?

Also, the claim for Kotlin Serialization uses 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


Originally published at https://vtsen.hashnode.dev.

💖 💪 🙅 🚩
vtsen
Vincent Tsen

Posted on June 3, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related