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

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.

Related

Issue IDE warning if annotated member is not surrounded with a particular block

I have a data structure which has members that are not thread safe and the caller needs to lock the resource for reading and writing as appropriate. Here's a minimal code sample:
class ExampleResource : LockableProjectItem {
override val readWriteLock: ReadWriteLock = ReentrantReadWriteLock()
#RequiresReadLock
val nonThreadSafeMember: String = ""
}
interface LockableProjectItem {
val readWriteLock: ReadWriteLock
}
fun <T : LockableProjectItem, Out> T.readLock(block: T.() -> Out): Out {
try {
readWriteLock.readLock().lock()
return block(this)
} finally {
readWriteLock.readLock().unlock()
}
}
fun <T : LockableProjectItem, Out> T.writeLock(block: T.() -> Out): Out {
try {
readWriteLock.writeLock().lock()
return block(this)
} finally {
readWriteLock.writeLock().unlock()
}
}
annotation class RequiresReadLock
A call ExampleResource.nonThreadSafeMember might then look like this:
val resource = ExampleResource()
val readResult = resource.readLock { nonThreadSafeMember }
To make sure that the caller is aware that the resource needs to be locked, I would like the IDE to issue a warning for any members that are annotated with #RequiresReadLock and are not surrounded with a readLock block. Is there any way to do this in IntelliJ without writing a custom plugin for the IDE?
I think this is sort of a hack, but using context receivers might work. I don't think they are intended to be used in this way though.
You can declare a dummy object to act as the context receiver, and add that as a context receiver to the property:
object ReadLock
class ExampleResource : LockableProjectItem {
override val readWriteLock: ReadWriteLock = ReentrantReadWriteLock()
// properties with context receivers cannot have a backing field, so we need to explicitly declare this
private val nonThreadSafeMemberField: String = ""
context(ReadLock)
val nonThreadSafeMember: String
get() = nonThreadSafeMemberField
}
Then in readLock, you pass the object:
fun <T : LockableProjectItem, Out> T.readLock(block: context(ReadLock) T.() -> Out): Out {
try {
readWriteLock.readLock().lock()
return block(ReadLock, this)
} finally {
readWriteLock.readLock().unlock()
}
}
Notes:
This will give you an error if you try to access nonThreadSafeMember without the context receiver:
val resource = ExampleResource()
val readResult = resource.nonThreadSafeMember //error
You can still access nonThreadSafeMember without acquiring a read lock by doing e.g.
with(ReadLock) { // with(ReadLock) doesn't acquire the lock, just gets the context receiver
resource.nonThreadSafeMember // no error
}
But it's way harder to accidentally write something like this, which I think is what you are trying to prevent.
If you call another function inside readLock, and you want to access nonThreadSafeMember inside that function, you should mark that function with context(ReadLock) too. e.g.
fun main() {
val resource = ExampleResource()
val readResult = resource.readLock {
foo(this)
}
}
context(ReadLock)
fun foo(x: ExampleResource) {
x.nonThreadSafeMember
}
The context receiver is propagated through.

Kotlin context receivers cannot resolve members of generic type

In the following code, the call member of Animal cannot be resolved even though Cat is specified as context receiver and it has a member named call.
interface Animal { val call: String }
object Cat : Animal { override val call: String = "Meow" }
object Dog : Animal { override val call: String = "Woof" }
fun <T : Animal> acquireAnimal(animal: T, block: context(T) () -> Unit) {
block(animal)
}
fun main() {
acquireAnimal(Cat) {
call
}
}
When I type this inside the lambda, then the IDE seems to suggest that the type of this is Any?.
If I do the same with a function without a generic context receiver, then it seems to get the type right.
Is this a limitation that is by design or is this a bug?
The fact that you cannot access call was a bug, which was fixed in Kotlin 1.7.20.
A workaround for lower versions is:
sealed interface TypeWrapper<out A> {
object IMPL: TypeWrapper<Nothing>
}
fun <T: Animal> acquireAnimal(animal: T, block: context(T) (TypeWrapper<T>) -> Unit) {
block(animal, TypeWrapper.IMPL)
}
fun main() {
acquireAnimal(Cat) {
val x = call // works!
}
}
However, the fact that this doesn't work is intended. Context receivers do not change the meaning of this. Since you are in a global function, this does not mean anything, and the existence of a context receiver does not change that.
Normally, to access the context receiver itself, you need to do a qualified this by appending the generated label for the context receiver:
context(Foo)
fun foo() {
val x = this#Foo
}
However, your context receiver is a type parameter, so according to the rules here, I don't think a label is generated for the context receiver.

How can I know which the subclass of sealed class will return when I use Compose in Android Studio?

The Result<out R> is a sealed class which hold three subclass Success, Error and Loading.
The fun Greeting is #Composable.
By my design, I define queryList as Result class, and it is assigned as Loading first, then it will be Success or Error.
1: But the following code can't be compiled as the following error information, what's wrong with my Code?
2: Is there a better solution for my design?
Compile error
Property delegate must have a 'getValue(Nothing?, KProperty>)' method. None of the following functions are suitable.*
#Composable
fun Greeting(
name: String,
mViewMode:SoundViewModel= viewModel()
) {
Column() {
//The following code cause error.
val queryList by produceState(initialValue = Result<Flow<List<MRecord>>>.Loading ) {
value = mViewMode.listRecord()
}
when (queryList){
is Loading -> { ...}
is Error -> { ...}
is Success -> {...}
}
}
}
class SoundViewModel #Inject constructor(): ViewModel()
{
fun listRecord(): Result<Flow<List<MRecord>>>{
return aSoundMeter.listRecord()
}
}
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}
Since queryList is backed by a delegate, it can not be final.
This means in theory, each time you access it, it might hold a different value. The kotlin compiler is very pessimistic about this and assumes that between the time the is Result.Success branch of your when statement is selected and val mydata = queryList.data is executed, the value of queryList might have changed.
To solve this, you can assign the current value of queryList to a final variable and work with that one instead:
when (val currentList = queryList) {
is Result.Error -> {}
is Result.Loading -> {}
is Result.Success -> {
SomeComposable(currentList.data) //currentList is properly smart-cast to Result.Success
}
}

Kotlin type inference on "supposedly" right types

I am new to Kotlin and I was playing with it. I pretty much wanted to create a pretty basic event bus. So I came up with this
interface Event
interface EventListener<E : Event> {
fun handle(event: E)
}
interface EventBus {
fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>)
}
class MyBus() : EventBus {
private val eventListeners: MutableMap<String, MutableList<EventListener<out Event>>> = mutableMapOf()
constructor(listeners: List<Pair<Class<Event>, EventListener<Event>>>) : this() {
listeners.forEach {
registerListener(it.first, it.second)
}
}
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val key = aClass.name
val listeners: MutableList<EventListener<out Event>> = eventListeners.getOrPut(key) { mutableListOf() }
listeners.add(eventListener)
}
}
val bus = MyBus(
listOf(
MyEvent::class.java to MyEventListener()
)
)
class MyEvent : Event
class AnotherEvent : Event
class MyEventListener : EventListener<MyEvent> {
override fun handle(event: MyEvent) {
}
}
what happens is that when I try to create MyBus using the constructor accepting the list of pairs, I get
Type inference failed. Expected type mismatch: inferred type is List<Pair<Class<MyEvent>,MyEventListener>> but List<Pair<Class<Event>,EventListener<Event>>> was expected
But if I change the constructor to be something like
constructor(listeners: List<Pair<Class<out Event>, EventListener<out Event>>>) : this() {
listeners.forEach {
registerListener(it.first, it.second)
}
}
adding out pretty much everywhere, then the MyBus constructor works, but the invocation to registerListener(..) breaks for the same exact reason as before. So the only way to solve this is to add "out"s also on registerListener function.
I suspect I'm doing something wrong here, but I don't know what precisely. Any help?
If you want your EventListener to be able to consume Events, then its type has to be invariant or covariant (not declared out). If it let you pass your EventListener<MyEvent> as if it were an EventListener<Event>, then your MyBus class might call listener.handle(event) on it with some Event that is not a MyEvent, such as AnotherEvent. Then you will get a ClassCastException when it tries to cast this AnotherEvent to MyEvent.
To be able to store different types of invariant EventHandlers, you will have to remove the variance restrictions by using star projection, and cast them when you retrieve them from the map. So make the map keys into class objects instead of just Strings. Since you will not have the help of the compiler when working with the star-projected types, you need to be careful that you are only adding an item to your MutableMap that is of the same type as the Class key that's associated with it. Then when you retrieve items, only cast to an invariant type.
The other part of your issue is that your constructor needs a generic type. Right now it works exclusively with Event so it can't handle subtypes of Event. Kotlin doesn't (yet?) support generic types for constructors so you have to do this with a factory function.
Here's an example of all the above.
class MyBus() : EventBus {
private val eventListeners: MutableMap<Class<*>, MutableList<EventListener<*>>> = mutableMapOf()
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val listeners = retrieveListeners(aClass)
listeners.add(eventListener)
}
private fun <E: Event> retrieveListeners(aClass: Class<E>): MutableList<EventListener<E>> {
#Suppress("UNCHECKED_CAST")
return eventListeners.getOrPut(aClass) { mutableListOf() } as MutableList<EventListener<E>>
}
}
// Factory function
fun <E : Event> myBusOf(listeners: List<Pair<Class<E>, EventListener<E>>>): MyBus {
return MyBus().apply {
listeners.forEach {
registerListener(it.first, it.second)
}
}
}
And you might want to change the type of the factory parameter from a <List>Pair to a vararg Pair so it's easier to use.
Here's a stripped down example to explain the variance limitation.
Your interface for an Event consumer:
interface EventListener<E : Event> {
fun handle(event: E)
}
Two implementations of Event:
class HelloEvent: Event {
fun sayHello() = println("Hello world")
}
class BoringEvent: Event {}
A class implementing the interface:
class HelloEventListener: EventListener<HelloEvent> {
override fun handle(event: HelloEvent) {
event.sayHello()
}
}
Now you have an EventListener that can handle only HelloEvents. Try to treat it like an EventListener<Event>:
val eventListener: EventListener<Event> = HelloEventListener() // COMPILE ERROR!
Imagine the compiler did not prevent you from doing this and you do this:
val eventListener: EventListener<Event> = HelloEventListener()
eventListener.handle(BoringEvent()) // CLASS CAST EXCEPTION AT RUN TIME!
If this were allowed your HelloEventListener would try to call sayHello() on the BoringEvent, which doesn't have that function, so it will crash. This is what generics are here to protect you from.
Now suppose your HelloEventListener.handle() didn't call event.sayHello(). Well, then it could have safely handled a BoringEvent. But the compiler isn't doing that level of analysis for you. It just knows what you declared, that HelloEventListener cannot handle anything except HelloEvent.

UninferredParameterTypeConstructor exception during build when generic parameters not specified explicitly

I have the following code (using RxKotlin 0.40.1):
class Result<T, E>(val data: T? = null, val error: E? = null)
fun <T, E> wrapResult(errorInfoFactory: (Throwable) -> E): (Observable<T>) -> Observable<Result<T, E>> = { it.map { Result<T, E>(it) }.onErrorReturn { Result<T, E>(error = errorInfoFactory(it)) } }
Just FYI this is needed to easily transform Observable which may throw errors to an Observable which always returns Result object, so onError is never triggered.
And I want to use it like this:
fun dangerousOperation() = 0
fun getErrorMessage(t: Throwable) = "error occurred"
fun test() {
val resultObservable = Observable.fromCallable(::dangerousOperation).compose(wrapResult(::getErrorMessage))
}
And Android Studio 1.5.1 with Kotlin plugin 1.0.0-release-IJ141-56 doesn't highlight any errors in this code. But when I try to build it, I get the error:
//Error:(24, 9) org.jetbrains.kotlin.codegen.CompilationException:
Back-end (JVM) Internal error: Error type encountered:
org.jetbrains.kotlin.types.ErrorUtils$UninferredParameterTypeConstructor#68754e6
(ErrorTypeImpl). //Cause: Error type encountered:
org.jetbrains.kotlin.types.ErrorUtils$UninferredParameterTypeConstructor#68754e6
(ErrorTypeImpl). //File being compiled and position: (24,9) in
/home/d/work/workspace/Hitch-hiking-Stats/app/src/main/java/ru/netimen/hitch_hikingstats/MemoryRepo.kt
//PsiElement: val resultObservable =
Observable.just(dangerousOperation()).compose(wrapResult(::getErrorMessage))
//The root cause was thrown at: JetTypeMapper.java:435 // at
org.jetbrains.kotlin.codegen.ExpressionCodegen.genQualified(ExpressionCodegen.java:299)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.genStatement(ExpressionCodegen.java:339)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.generateBlock(ExpressionCodegen.java:1532)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.generateBlock(ExpressionCodegen.java:1485)
// at
org.jetbrains.kotlin.codegen.CodegenStatementVisitor.visitBlockExpression(CodegenStatementVisitor.java:56)
// at
org.jetbrains.kotlin.codegen.CodegenStatementVisitor.visitBlockExpression(CodegenStatementVisitor.java:22)
// at
org.jetbrains.kotlin.psi.KtBlockExpression.accept(KtBlockExpression.java:44)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.genQualified(ExpressionCodegen.java:280)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.genStatement(ExpressionCodegen.java:339)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.gen(ExpressionCodegen.java:309)
// at
org.jetbrains.kotlin.codegen.ExpressionCodegen.returnExpression(ExpressionCodegen.java:1873)
// at
org.jetbrains.kotlin.codegen.FunctionGenerationStrategy$FunctionDefault.doGenerateBody(FunctionGenerationStrategy.java:50)
// at
org.jetbrains.kotlin.codegen.FunctionGenerationStrategy$CodegenBased.generateBody(FunctionGenerationStrategy.java:72)
// at
org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:364)
// at
org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:203)
// at
org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:138)
But when I change my test function to
fun test() {
val resultObservable = Observable.fromCallable(::dangerousOperation).compose(wrapResult<Int, String>(::getErrorMessage))
}
everything compiles OK. But why I get that exception when I don't specify the generic parameters of wrapResult explicitly if the IDE doesn't highlight any errors?
It is compiler bug: https://youtrack.jetbrains.com/issue/KT-11144.
Main reason why this bug is appear: function wrapResult uses generic parameter T only in own return type.