Search code examples
kotlingenericsexceptionbackground-servicekotlin-reified-type-parameters

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)
        }
    }
}

Solution

  • 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.