0
votes

in short : I want to create a generic class of Type T that should be calling a reified inline function of type T to be able to use Gson more generically

in details:
Hello. I had recently started using kotlin for Android dev, and i was trying to create a generic network client class. The class would fetch the data from server, convert the response into a datatype provided by the generic class's type and return the response, something like this:

class GenericNetworkService<RESPONSE_TYPE> {

    fun getDataFromServer(url: String): RESPONSE_TYPE {
        val request = Request.Builder().url(url).build()

        val response = OkHttpClient().newCall(request).execute()

        val convertor = Gson()

        val result: RESPONSE_TYPE = convertor.fromJson(response.body!!.string(), RESPONSE_TYPE)

        return result

    }
}

//calling
//fun main() {

//    val listOfPersons = GenericNetworkService<List<Person>>().getDataFromServer(url1)
//    println(listOfPersons)

//    val listOFTeacher = GenericNetworkService<List<Teacher>>().getDataFromServer(url2)
//    println(listOFTeacher)
// }

here , only the gson.fromJson() func is the one which is actually needing the details of the class in which the final output has to be converted . But it doesn't takes the generic type into second parameter, so the above program would not work. After searching through some internet articles, i came across thisTypeToken Api which helps achieve generic behavior upto certain extent:


inline fun <reified T> fromJson(json: String): T {
   return Gson().fromJson(json, object: TypeToken<T>(){}.type)
}

//calling : 
// val data : List<Teacher> = fromJson(response)

using inline reified functions, i am able to achieve everything i originally want:


private inline fun <reified RESPONSE_TYPE> getGenericRespSync(url: String): RESPONSE_TYPE {
    val okHttpClient = generateNetworkClient()
    val request = Request.Builder().url(url).build()
    val response: Response = okHttpClient.newCall(request).execute()

    val body: Reader = response.body!!.charStream()
    val finalResponse: RESPONSE_TYPE =  getConvertedListForResponse(body) //this is the same reified inline function as above with some non signinficant additions
    return finalResponse
}


But i want my other classes to call this function via some class instance. i.e something like GenericNetworkService<List<Person>>().getGenericRespSync(url1) should be returning List and not the direct call to getGenericRespSync(url1) . How can i achieve such functionality?

2

2 Answers

0
votes

Well, you can define

inline fun <reified RESPONSE_TYPE> GenericNetworkService<RESPONSE_TYPE>.getGenericRespSync1(url: String): RESPONSE_TYPE = getGenericRespSync<RESPONSE_TYPE>(url)

(I named the function differently so it can call your previously defined getGenericRespSync). Of course, it only uses the instance it's called on to get its type parameter and ignores it otherwise.

0
votes

Unfortunately, class constructors can not use the same reified mechanism as inline functions can. However, you can define your type as regular constructor parameter and create new instances of your class using a top level inline function

class GenericNetworkService<RESPONSE_TYPE>(private val responseTypeClass: Class<RESPONSE_TYPE>) {

    fun getDataFromServer(url: String): RESPONSE_TYPE {
        val request = Request.Builder().url(url).build()

        val response = OkHttpClient().newCall(request).execute()

        val convertor = Gson()

        val result: RESPONSE_TYPE = convertor.fromJson(response.body!!.string(), responseTypeClass)

        return result

    }
}

inline fun <reified RESPONSE_TYPE> createService() = GenericNetworkService(RESPONSE_TYPE::class.java)

Of course, you can also rename createService to GenericNetworkService so it looks like a constructor. This is becoming more common in kotlin, especially if you look at the coroutines core code, so I wouldn't consider this an anti-pattern anymore.