Why am I getting a java.lang.ClassCastException in Kotlin? - kotlin

I wrote this sample code that I extracted from a library that I'm making because it is causing a java.lang.ClassCastException and I don't understand why. Here's the code:
interface Event
data class Update(val message: Message?)
open class Message(open val text: String?)
data class TextMessage(override val text: String) : Message(text = text)
class MessageReceiveEvent(
val message: Message,
) : Event
class TextMessageReceiveEvent(
val message: TextMessage,
) : Event
fun handleUpdate(update: Update): Event {
if (update.message != null) {
if (update.message.text != null) return TextMessageReceiveEvent(update.message as TextMessage)
return MessageReceiveEvent(update.message)
}
error("Update message is null.")
}
handleUpdate(
Update(
message = Message(
text = "Text" // if this is null, the program successfully runs.
)
)
).let {
if (it is MessageReceiveEvent) println("Message without text.")
if (it is TextMessageReceiveEvent) println("Message with text.")
}

This code is not correct:
if (update.message.text != null)
return TextMessageReceiveEvent(update.message as TextMessage)
return MessageReceiveEvent(update.message)
You are trying to cast update.message as a TextMessage, but nothing guarantees that it is of this type. The fact that TextMessage happens to be very similar to Message apart from the nullability of the text property doesn't mean that every Message with non-null text is actually an instance of TextMessage.
You would have to actually confirm that with update.message is TextMessage.

Related

How to return boolean when using coroutines kotlin

I get the error The boolean literal does not conform to the expected type Unit when run code. Please help me fix it
public suspend fun RequireDevice(manager: UsbManager, device: UsbDevice): UsbDevice? {
// 多重要求にならないようにする - Tránh nhiều yêu cầu
if(permissionContinuation != null)
return null
// requestPermissionを実行
val device = suspendCoroutine<UsbDevice?> {
manager.requestPermission(device, mPermissionIndent)
permissionContinuation = it
}
// continuationを消去
permissionContinuation = null
return device
}
function selectDevice
fun selectDevice(vendorId: Int, productId: Int): Boolean {
if ((mUsbDevice == null) || (mUsbDevice!!.vendorId != vendorId) || (mUsbDevice!!.productId != productId)) {
closeConnectionIfExists()
connectScope.launch label#{
val usbDevices: List<UsbDevice> = deviceList
for (usbDevice: UsbDevice in usbDevices) {
if ((usbDevice.vendorId == vendorId) && (usbDevice.productId == productId)) {
Log.v(LOG_TAG, "Request for device: vendor_id: " + usbDevice.vendorId + ", product_id: " + usbDevice.productId)
closeConnectionIfExists()
val grantedDevice = RequireDevice(mUSBManager!!, usbDevice)
if (grantedDevice != null){
Log.v(LOG_TAG, "Connected")
state = STATE_USB_CONNECTING
mHandler?.obtainMessage(STATE_USB_CONNECTING)?.sendToTarget()
return#label true
}
else{
Log.v(LOG_TAG, "Connection failed.")
return#label false
}
}
}
}
} else {
mHandler?.obtainMessage(state)?.sendToTarget()
return true
}
}
BroadcastReceiver
private val mUsbDeviceReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if ((ACTION_USB_PERMISSION == action)) {
synchronized(this) {
val usbDevice: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
Log.i(
LOG_TAG,
"Success get permission for device ${usbDevice?.deviceId}, vendor_id: ${usbDevice?.vendorId} product_id: ${usbDevice?.productId}"
)
mUsbDevice = usbDevice
openConnection()
permissionContinuation!!.resume(usbDevice)
state = STATE_USB_CONNECTED
mHandler?.obtainMessage(STATE_USB_CONNECTED)?.sendToTarget()
} else {
permissionContinuation!!.resume(null)
Toast.makeText(context, "User refused to give USB device permission: ${usbDevice?.deviceName}", Toast.LENGTH_LONG).show()
state = STATE_USB_NONE
mHandler?.obtainMessage(STATE_USB_NONE)?.sendToTarget()
}
}
} else if ((UsbManager.ACTION_USB_DEVICE_DETACHED == action)) {
if (mUsbDevice != null) {
Toast.makeText(context, "USB device has been turned off", Toast.LENGTH_LONG).show()
closeConnectionIfExists()
state = STATE_USB_NONE
mHandler?.obtainMessage(STATE_USB_NONE)?.sendToTarget()
}
} else if ((UsbManager.ACTION_USB_DEVICE_ATTACHED == action)) {
}
}
}
Error:
e:USBPrinterService.kt: (139, 42): The boolean literal does not conform to the expected type Unit
Error 2:
e:USBPrinterService.kt: (143, 42): The boolean literal does not conform to the expected type Unit
Error 3:
e:USBPrinterService.kt: (154, 5): A 'return' expression required in a function with a block body ('{...}')
The boolean literal does not conform to the expected type Unit
You're facing this error because the lambda passed to launch is not expected to return any useful value. Its return type is Unit, which is used in functions for which we don't expect any particular result, and you're trying to return a Boolean out of it, which doesn't match the return type.
In order to return a value, you would need to use async instead of launch. This way, the lambda can indeed return a value (here, a Boolean), and the async function itself will return a Deferred<Boolean> representing the future boolean value that will get when the task completes. However, there is a deeper problem here that you need to understand first.
You have to understand that this function signature denotes a synchronous function:
fun selectDevice(vendorId: Int, productId: Int): Boolean {
...
}
It means it blocks the calling thread until the result is returned, because it's not suspend and the Boolean value is returned directly (not via a callback or a Future or Deferred).
However, inside this function, you're trying to launch an asynchronous task (with the launch function), but you somehow still want to return the result from it. That's not how launch works. Because the task is asynchronous, the call to launch returns before the task is completed, and possibly even before the task starts at all. As we've also seen above, launch doesn't return any value itself. In fact, even if you change it to async, the async { .. } function call will not return the value directly, but a Deferred<Boolean> (as we've seen). This wrapper would be returned immediately (before the task completes or even starts), so you would then have to wait for this value to be ready somehow anyway.
Basically, with the signature you chose for selectDevice, you don't have a choice but to block the current thread until the value is ready, which kinda defeats the purpose of coroutines. Instead, it would be best if you made selectDevice a suspend function itself, so you can call suspend functions inside without launching new coroutines.
If you want to stick with the blocking signature, then you have to block the thread to wait for the value, for instance using runBlocking { deferred.await() }.

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.

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

How to handle nullable types in generic class

I want to write a Promise class in kotlin. This class uses a generic type. The type can also be a nullable type. When I call the consumer, the value can be null, if the genereic type is nullable. If not the value must be set to an object. The kotlin compiler shows me following message:
Smart cast to 'T' is impossible, because 'value' is a mutable property that could have been changed by this time
I understand why this message appears but I am sure, that the value must be correct at this moment. How can I compile this class? I tried to force to access the value with !! but then my test with null will fail with a NPE.
java.lang.NullPointerException
at test.Promise.resolve(App.kt:20)
at test.AppTest.testPromiseNull(AppTest.kt:31)
class Promise<R> {
private var resolved = false
private var value: R? = null
var then: Consumer<R>? = null
fun resolve(r: R) = synchronized(this) {
if (resolved) error("Promise already resolved!")
value = r
resolved = true
then?.accept(value) // Smart cast to 'T' is impossible, because 'value' is a mutable property that could have been changed by this time*
}
fun then(consumer: Consumer<R>) = synchronized(this) {
if (then != null) error("Just one consumer is allowed!")
then = consumer
val value = value
if (resolved) {
consumer.accept(value) // Smart cast to 'T' is impossible, because 'value' is a mutable property that could have been changed by this time*
}
}
}
#Test fun testPromise() {
val promise = Promise<String>()
var resolved = false
promise.then {
assertEquals(it, "hello" )
resolved = true
}
promise.resolve("hello")
assert(resolved)
}
#Test fun testPromiseNull() {
val promise = Promise<String?>()
var resolved = false
promise.then {
assertNull(it)
resolved = true
}
promise.resolve(null)
assert(resolved)
}
UPDATE
I created a little help function
private fun <T> uncheckedCast(t: T?): T {
#Suppress("UNCHECKED_CAST")
return t as T
}
For your resolve function, you already have the immutable parameter value you can use instead.
fun resolve(r: R) = synchronized(this) {
if (resolved) error("Promise already resolved!")
value = r
resolved = true
then?.accept(r)
}
In the other case, since you know more than the compiler, you can do an explicit cast, and suppress the warning.
fun then(consumer: Consumer<R>) = synchronized(this) {
if (then != null) error("Just one consumer is allowed!")
then = consumer
if (resolved) {
#Suppress("UNCHECKED_CAST")
consumer.accept(value as R)
}
}

Type safety in Kotlin doesn't work as expected

I have a code like this:
enum class Player { PLAYER, COMPUTER }
interface BoardCell {
val x: Int
val y: Int
var player: Player?
}
data class Cell(val x: Int, val y: Int, var player: Player?, var value: Int)
data class BoardCellClass(override val x: Int, override val y: Int, override var player: Player?) : BoardCell
data class Request(val board: MutableList<MutableList<BoardCellClass>>? = null, val occupied: MutableList<BoardCellClass>? = null)
class AI(board: MutableList<MutableList<BoardCell>>, private var occupied: MutableList<BoardCell>) {
private var board: MutableList<MutableList<Cell>> = board.map { it.map { Cell(it.x, it.y, it.player, 0) } .toMutableList() } .toMutableList()
}
// in main
val request = call.receive<Request>()
if (request.board == null || request.occupied == null) {
// respond with 403
} else {
val ai = AI(request.board, request.occupied) // Kotlin: Type mismatch: inferred type is MutableList<MutableList<BoardCellClass>>? but MutableList<MutableList<BoardCell>> was expected
// Kotlin: Type mismatch: inferred type is MutableList<BoardCellClass>? but MutableList<BoardCell> was expected
}
But it errors with what is in the comment in the bottom. What am I doing wrong? Clearly, there is an if statement, that catches nullity, so it shouldn't be of type MutableList<MutableList<BoardCellClass>>?, but MutableList<MutableList<BoardCellClass>>, no?
Also, MutableList<MutableList<BoardCellClass>> is compatible with MutableList<MutableList<BoardCell>>, because it implements that interface, right?
MutableList> is compatible with
MutableList>, because it implements that
interface, right?
No. In your case you can use out keyword
class AI(board: MutableList<MutableList<BoardCell>>, private var occupied: MutableList<out BoardCell>) {
private var board: MutableList<MutableList<Cell>> = board.map { it.map { Cell(it.x, it.y, it.player, 0) } .toMutableList() } .toMutableList()
}
Clearly, there is an if statement, that catches nullity, so it
shouldn't be of type
You can write this code in if (not else) part to compiler understand you in wright way
if (request.board != null && request.occupied != null){
val ai = AI(request.board, request.occupied)
} else {
// respond with 403
}
Also, MutableList<MutableList<BoardCellClass>> is compatible with MutableList<MutableList<BoardCell>>, because it implements that interface, right?
No, it isn't. That's an issue of variance. Searching for "Kotlin variance" will give you many explanations, but the simplest way to see why MutableList<BoardCellClass> isn't a subtype of MutableList<BoardCell> is
val list: MutableList<BoardCellClass> = ...
val list1: MutableList<BoardCell> = list // illegal in actual Kotlin!
list1.add(object : BoardCell { ... }) // would add a BoardCell to a MutableList<BoardCellClass>
Then the same logic can be lifted up a level to see that MutableList<MutableList<BoardCellClass>> isn't a subtype of MutableList<MutableList<BoardCell>>.
Clearly, there is an if statement, that catches nullity, so it shouldn't be of type MutableList<MutableList<BoardCellClass>>?, but MutableList<MutableList<BoardCellClass>>, no?
That's not quite how Kotlin smart casts work (though the difference isn't relevant most of the time). It's still of type MutableList<MutableList<BoardCellClass>>?, but if it's used where the required type is MutableList<MutableList<BoardCellClass>>, it's automatically cast. Here the required type is MutableList<MutableList<BoardCell>> instead, and so there's no cast inserted and the nullable type shows up in the error message.