Daily dispatch #10 - Ktor is a Delight

Daily dispatch #10 - Ktor is a Delight

I recently started changing the project’s API client from Retrofit to Ktor.

And it’s been a delight. As a reference here’s the before and after code.

class MoviesAPIService {  
    private val API_KEY_MOVIEDB = "It's Mine You can't have it"  
    private val BASE_URL_MOVIEDB = "https://api.themoviedb.org/3/"  
    private val api = Retrofit.Builder()  
        .addConverterFactory(GsonConverterFactory.create()) // Converts JSON to GSON  
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // creates Single from GSON  
    fun getMovies(): Single<ManyMoviesResponse> {  
        return api.getMovies()  
    fun getMovie(id: String): Single<JsonObject> {  
        return api.getMovie(id)  
data class ManyMoviesResponse(  
    val page: Int,  
    val results: List<Movie>,  
    val total_pages: Int,  
    val total_results: Int  
typealias SingleMovie = Single<Movie>  
interface MoviesAPI {  
    fun getUnpopularMovies(): Single<ManyMoviesResponse>  
    fun getMovies(): Single<ManyMoviesResponse>  
    fun getMovie(@Path("movieId", encoded = true) movieId: String): Single<JsonObject  
class MovieDetailViewModel(application: Application) : BaseViewModel(application) {  
    fun fetchMovieFromRemote(id: Int) {  
        // loading.value = true  
                .subscribeWith(object : DisposableSingleObserver<JsonObject>() {  
                    override fun onSuccess(movie: JsonObject) {  
                        val newMovie = Movie(  
                            title = (movie["title"] as JsonPrimitive).asString,  
                            id = (movie["id"] as JsonPrimitive).asString,  
                            releaseDate = (movie["release_date"] as JsonPrimitive).asString,  
                            duration = (movie["runtime"] as JsonPrimitive).asString,  
                            director = getDirectorFromCredits(movie),  
                            description = (movie["overview"] as JsonPrimitive).asString,  
                            score = (movie["vote_average"] as JsonPrimitive).asString,  
                            imageUrl = (movie["poster_path"] as JsonPrimitive).asString,  
                        movieLiveData.value = newMovie  
                        Toast.makeText(getApplication(), "Movie retrieved", Toast.LENGTH_SHORT).show()  
                    override fun onError(e: Throwable) {  
                        // moviesLoadError.value = true  
                        // loading.value = false                        
    fun getDirectorFromCredits(creditsJsonObject: JsonObject): String {  
        try {  
            val credits =  
            val crew: JsonArray = (credits as JsonObject)["crew"] as JsonArray      
            var director = ""       
for(i in 0 until crew.size()) {  
                val currCrew: JsonObject =  
                    crew[i] as JsonObject       
                val currJob = currCrew["job"].asString       
                if(currJob == "Director") {  
                    director = currCrew["name"].asString       
            return director  
        } catch (e: Exception) {  
            return ""  

And I’d have to create those Single responses from RxJava2. Then there was the @GET annotations that made it a mess to put query parameters. Ugly as hell.

Then enters Ktor:

internal expect val ApplicationDispatcher: CoroutineDispatcher  
class KTorSimpleClient {  
    private val movieApiAddress = "https://api.themoviedb.org/3"   
	private val apiKey = "It's Mine you can't have it"  
    val client = HttpClient() {  
        install(ContentNegotiation) {  
            Json {  
                prettyPrint = true isLenient = true  
	suspend fun getMovies(): MovieListResult {  
        val httpResponse =  
            client.get { url("$movieApiAddress/discover/movie") parameter ("sort_by", "popularity.desc")     parameter("api_key", apiKey) }     return Json.decodeFromString(  
    suspend fun getMovie(id: String): Movie {  
        val httpResponse =  
            client.get { 
	            parameter ("append_to_response", "credits")     
	            parameter("api_key", apiKey) 
        val movie = Json.decodeFromString<Movie>(httpResponse.bodyAsText())  
        val director = getDirectorFromCredits(movie)   
        movie.director = director   
        return movie  
    private fun getDirectorFromCredits(movie: Movie): String {  
        return try {  
            val credits = movie.credits  
            val crew = credits.crew  
            var director = ""       
			for (element in crew) {  
                val currJob = element.job       
                if (currJob == "Director") {  
                    director = element.name     
        } catch (e: Exception) {  

It just looks delightful, it’s clean, it has it’s own Domain Specific Language, so very little Annotations required.

Why I love Ktor

Let’s go over why I love Ktor more than retrofit.

First of all Ktor is multiplatform.

Second, it uses Kotlin Coroutines instead of rxjava2.

So we get to develop a KMM module for our app straight out of the box.

Json to Data Class Plugin

Another useful thing I’ve found is the Json plug-in for Android Studio.

You give it a json response from an api and it generates kotlin data classes for ya.

Check out what it generated for me from this JSON:

         "original_title":"Ghostbusters: Afterlife",
         "overview":"When a single mom and her two kids arrive in a small town, they begin to discover their connection to the original Ghostbusters and the secret legacy their grandfather left behind.",
         "title":"Ghostbusters: Afterlife",
         "original_title":"The 355",
         "overview":"A group of top female agents from American, British, Chinese, Columbian and German government agencies are drawn together to try and stop an organization from acquiring a deadly weapon to send the world into chaos.",
         "title":"The 355",
         "overview":"A young street-smart, Nathan Drake and his wisecracking partner Victor “Sully” Sullivan embark on a dangerous pursuit of “the greatest treasure never found” while also tracking clues that may lead to Nathan’s long-lost brother.",

The generated Data Classes:

import kotlinx.serialization.SerialName    
import kotlinx.serialization.Serializable        

data class MovieListResult(     
    val page: Int,     
    val results: List<Result>,     
    val totalPages: Int,     
    val totalResults: Int    
data class Result(     
	val adult: Boolean,     
	val backdropPath: String,     
	val genreIds: List<Int>,     
	val id: Int,     
	val originalLanguage: String,     
	val originalTitle: String,     
	val overview: String,     
	val popularity: Double,     
	val posterPath: String,     
	val releaseDate: String,     
	val title: String,     
	val video: Boolean,     
	val voteAverage: Double,     
	val voteCount: Int    

Pretty Cool hm?

Manga Web Portal

On a more entrepreneurial note. I’ve been thinking about a simple app to buy and sell Manga with verified users and sellers.

Could be called Golduru.

I think I could set up a website on firebase and backend on it as well.

Maybe eventually link account with a block chain wallet to help verify scammers as well.

I think there is enough demand in this market to be able to sell this service as an yearly subscription.