I use this code in my project
fun getAllData(): List<Data> = writableDatabase.use { db ->
db.query(...)
}
It executes successfully on Lollipop devices, but on pre-Lollipop it throws a ClassCastException
FATAL EXCEPTION: main
java.lang.ClassCastException: android.database.sqlite.SQLiteDatabase cannot be cast to java.io.Closeable
Probably because it's Java 1.6 and doesn't have the try-with-resources feature, but I'm not sure.
So why do I have this exception and how can it be fixed?
The issue has nothing to do with try-with-resources and is simply an incompatible dependency issue between the compiled code and the runtime environment.
The problem is at compile time SQLiteDatabase
implements the interface Closeable
and later you swap this out for another implementation that does not. But the code is compiled already and doesn't know about this change.
When you call a function / method, all parameters are checked to make sure they are the correct types, this usually being handled by the JVM. For inline and extension functions the Kotlin compiler inserts a type check to make sure it is operating on the correct type, and for this specific inline function the receiver of the function is defined as being Closeable
. Here is the method signature:
inline fun <T : Closeable, R> T.use(block: (T) -> R): R { ... }
So the compiler inlines a type check on writableDatabase
as follows (in bytecode):
CHECKCAST java/io/Closeable
So at this point, the implementation of SQLiteDatabase
must forever implement the Closeable
interface or the compiled code will fail. By swapping to an older version of Android where this is no longer true, you break the contract and cause the exception. There is no way the compiler can do anything different. It would be the same as me swapping out a JAR in any JVM application to one with radically different implementations after I've already compiled my code. Changing Android versions basically swaps all the JARs.
What you can do to work around this problem is write your own use
function specifically for the SQLiteDatabase
class if all versions have a close
method (copied and modified from the Kotlin stdlib use
function):
inline fun <T : SQLiteDatabase, R> T.use(block: (T) -> R): R {
var closed = false
try {
return block(this)
} catch (e: Exception) {
closed = true
try {
close()
} catch (closeException: Exception) {
// eat the closeException as we are already throwing the original cause
// and we don't want to mask the real exception
}
throw e
} finally {
if (!closed) {
close()
}
}
}
Now the inline typecheck will be CHECKCAST android/database/sqlite/SQLiteDatabase
which will always succeed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With