Kotlin class generic type infered as nullable - kotlin

A generic class for holding network request result
sealed class Result<out T : Any?> {
data class Success<out T : Any?>(val data: T) : Result<T>()
data class Error(val message: String, val exception: Exception? = null) : Result<Nothing>()
}
A generic function for encapsulating network result into Result.
It is called from a repository and passes a retrofit2 api call as an input parameter
suspend fun <T: Any?> request(method: Call<T>): Result<T> {
return withContext(Dispatchers.IO) {
try {
val response = method.awaitResponse() // Retrofit2 Call
if (response.isSuccessful)
Result.Success(response.body())
else
response.getErrorResult()
} catch (e: Exception) {
Result.Error(e.message.orEmpty(), e)
}
}
// Type mismatch.
// Required: Result<T>
// Found: Result<T?>
}
It is called like this
interface Webservice {
#GET("data")
fun getData(): Call<Data> // Retrofit2
}
suspend fun getData(): Result<Data> {
return request(webservice.getData())
}
Why does it infer the result as type T? but not T?

The problem is this line:
Result.Success(response.body())
body is marked with Nullable, so when ported to Kotlin, response.body() returns a T?, rather than T. See here for how this works.
Therefore, the type parameter for Result.Success is inferred to be T?, and so the expression above creates a Result<T?>. Note that the T in Result<T?> refers to the type parameter of request.

Related

Access Data class property while using Generic Type parameter <T> in runtime [Kotlin]

I'm trying to return a property/value from Data class by checking type parameter
Data Class :
data class SystemConfiguration(
val systemName: String,
val fields: List<String>
)
Abstract class :
abstract class ConfigurationLoader<out T> {
abstract val clazz: Class<out T>
private fun getAssociateAttribute(file: T): String{
return when(clazz){
SystemConfiguration::class.java -> file.systemName // Line causing error
// If its another Data class, I should return value from that data class
else -> ""
}
}
open fun loadConfigurations(): Map<String, T>{
val map = Paths.get(configurationFolderPath, filePath).toFile().walkTopDown().filter { it.isFile }.map {
val x = ionSystem.loader.load(it)[0]
ionValueMapper.readValue<List<T>>(x.toString())
}.flatten().associateBy { getAssociateAttribute(it) }
}
}
inline fun <reified T: Any> javaClasstype(): Class<T> {
return T::class.java
}
Sub class
class ServiceConfigurationLoader (
override val clazz: Class<SystemConfiguration> = javaClasstype()
): ConfigurationLoader<SystemConfiguration>()
I'm getting an exception "e: Unresolved reference: systemName". Not able to access values inside data class while we use type parameter
If i use like this(directly mentioning data class name), I'm able to access the values
private fun getAssociateAttribute(file: SystemConfiguration): String{
return when(clazz){
SystemConfiguration::class.java -> file.systemName
else -> ""
}
}
Could someone help me out here to access the value using Type paramater in Kotlin?
Thanks in Advance !!
I have tried using reified keyword as well. Still getting the same issue. I'm expecting to access the value using Type paramater

why lambda function parameter's type is Nothing on generic type with asterisk in kotlin?

when i call some api, i wished use multiple callback with generic parameter.
so i defined CallBackData class
class CallBackData<T>(val func: (T?) -> Boolean, val params: T?)
it not data class. because it super class of other callbacks.
and i define Array<CallBackData<*>> variable for multiple callback.
val callbackDts : Array<CallBackData<*>> = arrayOf(
CallBackData(::sampleCallback1, SomeClass(1)),
CallBackData(::sampleCallback2, "hello"),
CallBackData(::sampleCallback3, -1),
)
but when i call func, it say error
Type mismatch.
Required: Nothing?
Found: Any?
i don't get it. why? isn't same it.params type T is same of it.func(param(T))? right? why is Nothing Type? why is not same?
this is full code
fun start(){
val callbackDts : Array<CallBackData<*>> = arrayOf(
CallBackData(::sampleCallback1, SomeClass(1)),
CallBackData(::sampleCallback2, "hello"),
CallBackData(::sampleCallback3, -1),
)
callApi(callbackDts)
}
fun callApi(callbacks : Array<CallBackData<*>>){
callbacks.forEach{
it.func(it.params)
}
}
fun sampleCallback1(params: SomeClass?) : Boolean {
println("sampleCallback1 ${params.toString()}")
return true
}
fun sampleCallback2(params: String?) : Boolean {
println("sampleCallback2 $params")
return true
}
fun sampleCallback3(params: Int?) : Boolean {
println("sampleCallback3 $params")
return true
}
data class SomeClass(val i:Int)
class CallBackData<T>(val func : (T?) -> Boolean, val params: T?)
i tried convert to like this (using out keyword), but it's failed same.(Lambda's parameter type is Nothing?)
fun start(){
val callbackDts : Array<CallBackData<out Any?>> = arrayOf(
CallBackData(::sampleCallback1, SomeClass(1)),
CallBackData(::sampleCallback2, "hello"),
CallBackData(::sampleCallback3, -1),
)
callApi(callbackDts)
}
fun callApi(callbacks : Array<CallBackData<out Any?>>){
callbacks.forEach{
it.func(it.params)
}
}
fun sampleCallback1(params: SomeClass?) : Boolean {
println("sampleCallback1 ${params.toString()}")
return true
}
fun sampleCallback2(params: String?) : Boolean {
println("sampleCallback2 $params")
return true
}
fun sampleCallback3(params: Int?) : Boolean {
println("sampleCallback3 $params")
return true
}
data class SomeClass(val i:Int)
class CallBackData<T>(val func : (T?) -> Boolean, val params: T?)
i look forward to your reply. thanks!
Unfortunately, the type information of T is gone once you projected a CallbackData<T> to CallbackData<*>. It is no longer known that it.func takes the same type as it.params.
But you do know that they are the same type in the CallBackData class itself, don't you? So you can just add a call method
class CallBackData<T>(val func : (T?) -> Boolean, var params: T?) {
fun call() = func(params)
}
and
callbacks.forEach{
it.call()
}
Or you can overload the invoke operator:
operator fun invoke() = func(params)
You would then be able to do it() directly.
Even if you don't have control over CallBackData, you can still add an extension function:
operator fun <T> CallBackData<T>.invoke() = func(params)
Adding to other answers: if this is the only reason why you defined the CallBackData, then you don't really need this class. Kotlin has support for closures, so we don't need to intercept functions and parameters separately:
fun start(){
val callbackDts = arrayOf<() -> Unit>(
{ sampleCallback1(SomeClass(1)) },
{ sampleCallback2("hello") },
{ sampleCallback3(-1) },
)
callApi(callbackDts)
}
fun callApi(callbacks : Array<() -> Unit>){
callbacks.forEach{
it()
}
}
You can define a function
fun <T> CallBackData<T>.call() = func(params)
and then callApi can be changed to:
fun callApi(callbacks : Array<CallBackData<*>>){
callbacks.forEach{ it.call() }
}
Then Kotlin does not have a problem to infer that the types of func and params match for each CallBackData.

Ktor reified type parametar

I created class with generic in kotlin and want to use receive with generic, but I have error when i want to call.recieve type from generic:
Can not use MType as reified type parameter. Use a class instead.
Code:
class APIRoute<EType : IntEntity, MType : Any> {
fun Route.apiRoute() {
post {
val m = call.receive<MType>()
call.respond(f(model))
}
}
}
How to fix it?
You need to provide the expected type to the receive() function. Due to type erasure in Java/Kotlin, the type of MType is unknown at runtime, so it can't be used with receive(). You need to capture the type as KType or KClass object when constructing APIRoute.
KClass is easier to use, however it works with raw classes only, it doesn't support parameterized types. Therefore, we can use it to create e.g. APIRoute<*, String>, but not APIRoute<*, List<String>>. KType supports any type, but is a little harder to handle.
Solution with KClass:
fun main() {
val route = APIRoute<IntEntity, String>(String::class)
}
class APIRoute<EType : IntEntity, MType : Any>(
private val mClass: KClass<MType>
) {
fun Route.apiRoute() {
post {
val m = call.receive(mClass)
call.respond(f(model))
}
}
}
Solution with KType:
fun main() {
val route = APIRoute.create<IntEntity, List<String>>()
}
class APIRoute<EType : IntEntity, MType : Any> #PublishedApi internal constructor(
private val mType: KType
) {
companion object {
#OptIn(ExperimentalStdlibApi::class)
inline fun <EType : IntEntity, reified MType : Any> create(): APIRoute<EType, MType> = APIRoute(typeOf<MType>())
}
fun Route.apiRoute() {
post {
val m = call.receive<MType>(mType)
call.respond(f(model))
}
}
}

How do I override sealed class fields?

I created a custom Result field (And not Kotlin's Result) in my service so I can return a message field in Both Success & Failure Cases:
sealed class Result<T> {
data class Success<T>(val value: T, val message: String) : Result<T>()
data class Failure<T>(val throwable: Throwable? = null, val message: String) : Result<T>() {
val isExceptional = throwable != null
val error: Throwable
get() = throwable ?: error("Error is undefined in [$this]")
}
}
Then in another class I called a method that produces this result and wanted to log Result.message
logger.info { "Finished with message [${result.message}]." }
Only, kotlin compiler doesn't recognize "message" since it's not a property of Result directly, but a property of Success and Failure.
I tried to override the message field and define it in Result class. But I get an Error.
Error:(10, 38) Kotlin: 'message' in 'Result' is final and cannot be overridden
So, how can one accesss Result.message without casting the result instance to it's derived implementing class (Success or Failure)?
A clean solution that I found is the following.
While Kotlin does not allow us to override sealed class members. It does allow us to override interface members.
So, I Created a simple interface for the message field, and implemented it from Result class:
interface ResultMessage {
val message: String
}
sealed class Result<T> : ResultMessage
// Now I'm able to override the message field simply, with no error
data class Success<T>(val value: T, override val message: String) : Result<T>()
data class Failure<T>(val throwable: Throwable? = null, override val message: String) : Result<T>() {
val isExceptional = throwable != null
val error: Throwable
get() = throwable ?: error("Error is undefined in [$this]")
}
You can declare an abstract property in the Result class:
sealed class Result<T> {
abstract val message: String
data class Success<T>(val value: T, override val message: String) : Result<T>()
data class Failure<T>(val throwable: Throwable? = null, override val message: String) : Result<T>() {
val isExceptional = throwable != null
val error: Throwable
get() = throwable ?: error("Error is undefined in [$this]")
}
}
Though, your solution is also an option
1- you can mark them open
open val message: String = ""
2- you can define them abstract
abstract val message: String

Type inference only works for extension function

The following code works fine and the call to the foo.get() extension function returns the correct type BarImpl.
open class Bar
class BarImpl: Bar()
class Foo<T : Bar>
inline fun <reified T : Bar> Foo<T>.get(): T {
return SomeMap(this).get(T::class)
}
class Activity {
lateinit var foo: Foo<BarImpl>
val barImpl = foo.get()
}
But when I try to move Foo<T>.get() into the class the type inference fails
class Foo<T : Bar> {
inline fun <reified T : Bar> get(): T {
return SomeMap(this).get(T::class)
}
}
class Activity {
lateinit var foo: Foo<BarImpl>
val barImpl = foo.get()
}
error: type inference failed: Not enough information to infer parameter T in inline fun get(): T
Please specify it explicitly.
val vm = foo.get()
^
How can I move the function into the class?
The extension function returns the result of the Foo type parameter. So the result type can be inferred from the receiver type.
And the member function result type has nothing in common with Foo type parameter except the name, which means nothing for a compiler. You can see that T in method and T in class are different types by writing and compiling the following code:
Foo<BarImpl>().get<BarImpl2>()
If you want to make get to be a member function which returns the result of Foo type parameter, you should remove type parameter from function and inject class instance via the constructor:
class Foo<T : Bar>(private val clazz: KClass<T>) {
fun get(): T {
return SomeMap(this).get(clazz)
}
companion object {
inline operator fun <reified T : Bar> invoke() = Foo(T::class)
}
}