1
\$\begingroup\$

As part of an android app, I've written a simple wrapper that makes requests to an api and fetches stuff.

import android.app.Activity
import android.util.Log
import android.widget.Toast
import com.github.kittinunf.fuel.core.Parameters
import com.github.kittinunf.fuel.httpGet
import com.github.kittinunf.fuel.json.responseJson
import com.github.kittinunf.result.Result
import com.google.gson.Gson


data class Xkcd(val id: Int, val content: String, val link: String, val title: String)


class XkcdClient(private val main: Activity) {
    private val API = "https://279fbce3.ngrok.io"

    fun search(p: Parameters, callback: (Array<Xkcd>) -> Unit) {
        makeRequest("$API/search", p, callback)
    }

    private inline fun <reified T> makeRequest(url: String, p: Parameters, crossinline callback: (T) -> Unit) {
        buildPath(url, p)
            .httpGet()
            .responseJson { _, _, result ->
                when (result) {
                    is Result.Failure -> {
                        Log.i("failed", "request failed")
                        main.runOnUiThread {
                            Toast.makeText(
                                main,
                                "Request Failed!",
                                Toast.LENGTH_LONG
                            ).show()
                        }
                    }
                    is Result.Success -> {
                        val json = result.get().obj()
                        Log.i("json", json.toString())
                        val res = deserialize<T>(json.get("results").toString())
                        main.runOnUiThread { callback(res) }
                    }
                }
            }
    }
}

inline fun <reified T> deserialize(content: String): T {
    return Gson().fromJson(content, T::class.java)
}

fun buildPath(url: String, params: Parameters): String {
    var url = url
    if (params.isNotEmpty()) {
        url += "?"
    }

    for ((p, v) in params) {
        url += "$p=$v&"
    }
    return url.substring(0, url.lastIndex)
}

It works as I want it to. As such, I'm looking for some advice on how I can make it more idiomatic/elegant.


Just FYI: I've written the buildPath myself, as the http library I'm using (kittinuf.fuel) has a bug in its parameter setting method. When it's fixed, I'll be switching to its (hopefully correct) implementation. My method has at least one known bug in it, where if the base url has a trailing slash, it will return a url with double slashes. To avoid getting into all hairy edges cases, I've made sure my hardcoded base url is fine and stripped of a trailing slash.

\$\endgroup\$
2
\$\begingroup\$

You should look into coroutines.
Coroutines are the Promises from JavaScript and the CompleteableFutures of Java.
It's basically Kotlin handeling the callbacks for you.

suspend fun getString() : String {
    return "t"
}
suspend fun main() {
    val t = getString()
    println(t)
}
//is roughly the same as:
fun getString(lamb: (String)->Unit){
    lamb("t")
}
fun main() {
    getString{
        println(t)
    }
}

You don't really need to know the implementation, but just some rules:

suspend functions can only be called from suspend-functions (because of the hidden parameter)
Suspend functions allow you to replace lammbda's with normal code, even try catch blocks!!!

in our case, we can change makeRequest to use suspension functions:

private inline suspend fun <reified T> makeRequest(url: String, p: Parameters) {
    val json = buildPath(url, p)
        .httpGet()
        .awaitObjectResult(jsonDeserializer()).obj()
    Log.i("json", json.toString())
    return deserialize<T>(json.get("results").toString())
}

change the search with:

suspend fun search(p: Parameters) = makeRequest("$API/search", p, callback)

then in MainActivity:

class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    override fun onDestroy() {
        cancel() // cancel is extension on CoroutineScope
    }

    //you can compare using launch with having a second thread.
    fun showSomeData() = launch { 
        try {
            val text = XkcdClient(private val main: Activity).search(..)
        } catch (e : Exception) {
            Log.i("failed", "request failed")
            Toast.makeText(
                main,
                "Request Failed!",
                Toast.LENGTH_LONG
            ).show()
        }
    }
}

extra dependencies:

  • implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
  • implementation 'com.github.kittinunf.fuel:fuel-coroutines:'
  • implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'

extra info

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.