简体   繁体   中英

Android Room returns Null as Non-Null type

I have Dao to return simple object. If object does not exist, Room return null , but Android app have no crashes. Also if I assign that value to non-null variable, no crashes are in app.

Dao:

@Query("SELECT * FROM users WHERE id LIKE :id LIMIT 1")
abstract fun getById(id: Long): User

not crashing code:

doAsync {
    val user: User = userDao.getById(999)   // user 999 not exist, userDao returns null
    uiThread {
        if (user == null) {
            Timber.d("user is $user")   // "user is null" in log
        } else {
            Timber.d("user is ${user.email}")
        }
    }
}

I have two questions:

  1. How is possible that Room can return Null value as Non-Null variable?
  2. How is possible that code with assign null to Non-Null variable has no crashes ?

It's all about how Kotlin handle nulls on boundaries with Java code.

If you pass null from Java to Kotlin method that require non-null value you will get exception. This is achieved with calls to Intrinsics.checkNotNull function added by Kotlin compiler in beginning of method.

For example:

fun hello(who: String): Unit {
    println ("Hello $who")
}

becomes

public final void hello(@NotNull String who) {
    Intrinsics.checkParameterIsNotNull(who, "who");
    String var2 = "Hello " + who;
    System.out.println(var2);
}

Similar check added when you call Java methods from Kotlin.

But in you case you have Kotlin interface and it's implementation in Java generated by Room. So Kotlin compiler can't add checks because it has no control on all implementations of interface. Otherwise it would have to add checks after every call to Kotlin class or interface because it can be implemented in Java, what is bad for performance.

UPD: found similar issue at Room bugtracker https://issuetracker.google.com/issues/112323132 . Googler said that this is intended behavior and if you wrote query that can return null value, then you are responsible to mark it as nullable in dao interface.

As I understand your generated Dao class didn't annotate return value as @Nullable in Java code.

@Override
public User getById(long id) {

should be

@Override
public @Nullable User getById(long id) {

as a result you don't see warning

The reason you're not getting a NullPointerException is simple: Room is a Java library, not Kotlin, so it has no idea your User should not be null. When interfacing with Java code, Kotlin introduces some null checks to give you that exception, but since your interface is written in Kotlin it assumes that won't be a problem and skips the check.

I'm not a Room expert, but after a quick google search, I couldn't find a way of forcing Room to check for nulls. I see two ways of solving your "problem":

  • Change the getById function to return User?
  • Convert your @Dao to a Java interface, forcing Kotlin to check for null.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM