getting retrofit exception Method return type must not include a type variable or wildcard in kotlin - kotlin

networkCall = NetworkCall(context)
val responceCall = networkCall!!.getRetrofit(true).callReadMeService()
responceCall.clone().enqueue(object : Callback<BaseResponse<*>?> {
override fun onResponse(call: Call<BaseResponse<*>?>, response: Response<BaseResponse<*>?>) {
networkCall!!.dismissLoading()
web_view!!.loadData((response.body()?.data as LinkedTreeMap<*, *>)["description"] as String, "text/html", "UTF-8")
}
override fun onFailure(call: Call<BaseResponse<*>?>, t: Throwable) {
networkCall!!.dismissLoading()
}
})
here is api method
#POST("stories/readme")
fun callReadMeService(): Call<BaseResponse<*>?>
now i am getting this exception
here is my BaseResponse class
class BaseResponse<T> {
var message: String? = null
var status: Boolean = false
var errors: Array<String>? = null
var code: String? = null
var data: T? = null
}
Method return type must not include a type variable or wildcard: retrofit2.Call>
for method IApi.callReadMeService
now i can't remove generic data variable from BaseResponse class, cause i am using this class as a common Api parser
any solution for this

You cannot do this, because the type info needs to be fully specified, otherwise retrofit cannot correctly generate the Service. See this discussion.
You need to create a different API method for each type.

Related

Kotlin class generic type infered as nullable

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.

Inline function with reified generic throws illegal type variable reference exception when used in background service

I have an inline function using a reified generic like the following. It is inside of a companion object, therefore static:
inline fun <reified T> getListFromPreferences(preferences : SharedPreferences, key : String)
: MutableList<T> {
return try {
val listAsString = preferences.getString(key, "")
val type: Type = object : TypeToken<List<T>>() {}.type
val gson = SMSApi.gson
gson.fromJson<ArrayList<T>>(listAsString, type)
?: ArrayList()
}catch(exception: JsonSyntaxException) {
ArrayList()
}
}
When I test it with an instrumented test and when I use it in the app itself, it works perfectly fine. However, when I call the function in a background service, it throws a fatal exception, saying it is an illegal type variable reference, quitting the app:
E/AndroidRuntime: FATAL EXCEPTION: Thread-10
Process: example.app, PID: 20728
java.lang.AssertionError: illegal type variable reference
at libcore.reflect.TypeVariableImpl.resolve(TypeVariableImpl.java:111)
at libcore.reflect.TypeVariableImpl.getGenericDeclaration(TypeVariableImpl.java:125)
at libcore.reflect.TypeVariableImpl.hashCode(TypeVariableImpl.java:47)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.hashCode($Gson$Types.java:595)
at java.util.Arrays.hashCode(Arrays.java:4074)
at com.google.gson.internal.$Gson$Types$ParameterizedTypeImpl.hashCode($Gson$Types.java:502)
at com.google.gson.reflect.TypeToken.<init>(TypeToken.java:64)
at example.app.NotificationService$remoteNotificationReceived$$inlined$let$lambda$1$1.<init>(PreferenceHelper.kt:16)
at example.app.NotificationService$remoteNotificationReceived$$inlined$let$lambda$1.run(NotificationService.kt:63)
at java.lang.Thread.run(Thread.java:764)
inline fun <reified T> getListFromPreferences(preferences : SharedPreferences, key : String)
: MutableList<T> {
return try {
val listAsString = preferences.getString(key, "")
val type: Type = object : TypeToken<List<T>>() {}.type
val gson = SMSApi.gson
gson.fromJson<ArrayList<T>>(listAsString, type)
?: ArrayList()
}catch(exception: JsonSyntaxException) {
ArrayList()
}
}
The background service is a NotificationService implementing the OSRemoteNotificationReceivedHandler of OneSignal. The function throws the exception in the onNotificationReceived() method.
Is there any reason I donĀ“t understand, why inlining in the application (foreground) is fine, but throws an exception in the background? Or any way to solve this?
EDIT:
Sharing the notificationService, that invokes it:
class NotificationService : OneSignal.OSRemoteNotificationReceivedHandler {
override fun remoteNotificationReceived(context: Context?, notificationReceivedEvent: OSNotificationReceivedEvent?) {
notificationReceivedEvent?.let {
val data = notificationReceivedEvent.notification.additionalData
if(context != null) {
//Fetch some vals
Thread {
val result = //Insert data in db
//-1 will be returned, for rows that are not inserted.
//Rows will not be inserted, if they hurt a unique constraint.
//Therefore the following code should only be executed, when it is inserted.
if(result[0]!=-1L) {
//Get preferences, create item
val list = PreferenceHelper
.getListFromPreferences<MessageAcknowledgement>
(preferences, App.ACKNOWLEDGE_IDS) -> throws error
list.add(acknowledgeMessage)
PreferenceHelper.setListInPreferences(preferences,
App.ACKNOWLEDGE_IDS, list)
//Do some more stuff
}
}.start()
}
Log.d("NotificationService", data.toString())
notificationReceivedEvent.complete(notificationReceivedEvent.notification)
}
}
}
I'm not sure what is the problem with the above code, it would require sharing more of it, but Kotlin has a native way of acquiring Type tokens. Just replace:
object : TypeToken<List<T>>() {}.type
with:
typeOf<List<T>>().javaType
As typeOf() is still experimental, you need to annotate your function with: #OptIn(ExperimentalStdlibApi::class). I use it for some time already and never had any problems, so I guess it is pretty safe to use, at least on JVM.

Jackson Databind not setting default value

In my quarkus application I have an endpoint that takes in a DTO, with a field that has a default value. When I don't send that field, I still get the exception
com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of
`FooDTO`, problem: Parameter specified as non-null is null: method
io.otherstuff.FooDTO.<init>, parameter someListVariable
at [Source: (io.quarkus.vertx.http.runtime.VertxInputStream); line: 4, column: 1]
The class looks like this:
class FooDTO(
override var someStringVar: String,
override var someListVariable: List<Int> = emptyList(),
): BarDTO
---------------------------------------------
interface BarDTO {
var someStringVar: String
var someListVar: List<Int>
}
Now if I send a payload like this
{
"someStringVar": "Hello Stackoverflow",
"someListVar": []
}
it is working perfectly fine, but when I drop "someListVar" I get the exception from above, even though it should just initialize it as an empty list.
Any help is much appreciated!
The problem is, that during desalinization, the library (fasterxml) calls the primary constructor with null: FooDTO("Hello Stackoverflow", null). The call ends up with the exception as the someListVariable parameter is not nullable (default value is used only when the paremeter is not provided at all, not when it's null).
One option of solving the problem would be providing an explicit JsonCreator:
class FooDTO(
override var someStringVar: String,
override var someListVariable: List<Int> = emptyList()) : BarDTO {
companion object {
#JvmStatic
#JsonCreator
fun of(
#JsonProperty("someStringVar") someStringVar: String,
#JsonProperty("someListVariable") someListVariable: List<Int>?) =
FooDTO(someStringVar, someListVariable ?: emptyList())
}
}
Another posibility is using secondary constructor instead of the default value:
class FooDTO : BarDTO {
override var someStringVar: String
override var someListVariable: List<Int>
#JsonCreator
constructor(
#JsonProperty("someStringVar") someStringVar: String,
#JsonProperty("someListVariable") someListVariable: List<Int>?) {
this.someStringVar = someStringVar
this.someListVariable = someListVariable ?: emptyList()
}
}
Both options are unfortunately a bit verbose.

Not enough information to infer parameter T

Context: I am building a REST API with kotlin using Spring
Problem: I have a Kotlin class called Response that accepts a generic like this:
class Response<T> {
var data: T? = null
var dataArray: List<T>? = null
var errors: List<String>? = null
get() {
if (field == null) {
this.errors = ArrayList()
}
return field
}
}
When I try to instantiate in one of my API Controllers like this:
val response = Response()
response.setData(someting)
It gives me Not enough information to infer parameter T.
How can I avoid this error?
You will have to specify what T is in this case. Supposing it is a String, you could do it like this:
val response = Response<String>()
response.data = "Something that is a String"

Casting from Any to another class returns null in Kotlin

I have a dynamic Json and I am trying to parse it like this
obs.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(object: Observer <Response<Any>> {
override fun onComplete() {}
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: Response<Any>) {
if (t.code() != 400) {
val registerResponse = t.body() as RegisterResponse?
//this is null
println("value of register resp " + registerResponse?.status)
liveData.value = t.body() as RegisterResponse ?
} else {
val validationError = t.body() as ValidationError?
//this is also null
println("error resp " + validationError?.message?.password?.get(0))
error.value = t.body() as ValidationError?
}
}
override fun onError(e: Throwable) {}
})
I am trying to get a generic Response as Any and then if the response returns a valid response I cast it to RegisterResponse and if it returns error then I cast it to ValidationError response. But when I am doing this, it is returning null every time. What am I doing wrong here?
Any specifies and assures your instance t is not NULL. The only explanation is that t.body() is null, and that it is a nullable type. There is not enough info here to solve why.
If your are certain it should not be, cast to non-null types
val registerResponse = t.body() as RegisterResponse
val validationError = t.body() as ValidationError
That will make your code much cleaner downstream as well, and you can remove all the null guards.
You will also get an exception instead of falling through.