realm / realm-kotlin

Kotlin Multiplatform and Android SDK for the Realm Mobile Database: Build Better Apps Faster.
Apache License 2.0
944 stars 57 forks source link

Evaluate Expression in Android Studio does not work correctly with Realm Objects #715

Open CarsonRedeye opened 2 years ago

CarsonRedeye commented 2 years ago

Typing this in the Evaluate Expression window in the debugger in Android studio aRealmObject.someRealmList.first() throws a NoSuchElementException: List is empty, when the realm list really isn't empty.

Android Studio Bumblebee | 2021.1.1 Patch 1 Build #AI-211.7628.21.2111.8139111, built on February 2, 2022 Runtime version: 11.0.11+0-b60-7590822 x86_64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o. macOS 12.0.1 GC: G1 Young Generation, G1 Old Generation Memory: 4096M Cores: 16 Registry: external.system.auto.import.disabled=true Non-Bundled Plugins: com.chimerapps.proguard-retrace (1.0.1), com.cmgapps.intellij.proguard-retrace-unscambler (1.3.0), wu.seal.tool.jsontokotlin (3.7.2), org.jetbrains.kotlin (211-1.6.10-release-923-AS7442.40)

realm version 0.7.0 (we currently can't upgrade higher than this due to kotlin 1.6 issues.

nhachicha commented 2 years ago

Hi @CarsonRedeye When using a variable watch or the evaluate window, make sure you're accessing the backing field which has the data fetched from the DB. In the screenshot below I'm doing a query using the Bookshelf sample which contains a RealmList, notice that authors backing field (in the green colour) is populated (managed) whereas the property authors is an unmanaged list (size 0)

Screenshot 2022-03-04 at 04 42 10

another example https://youtrack.jetbrains.com/issue/KTIJ-1243

CarsonRedeye commented 2 years ago

I did notice that this is available in the watches, but there doesn't seem to be any way to evaluate an expression with the backing_field. I tried aRealmObject.some_realm_list.first() but still it is empty

nhachicha commented 2 years ago

I think this is an issue with the IDE, since you can see an auto-completion with the backing field (when using evaluate)

image

But when you select it, it is considered an unresolved reference

image

@cmelchior have you experienced similar behaviour when playing with IDE plugin?

CarsonRedeye commented 2 years ago

I don't get an unresolved reference in the Android Studio version etc mentioned above

Screen Shot 2022-03-04 at 3 55 34 pm

cmelchior commented 2 years ago

I haven't experienced this issue before, but I can reproduce in our Bookshelf app running 0.7.0:

image

Not 100% sure what is going on, but it looks like a bug in Android Studio somehow 🤔

cmelchior commented 2 years ago

It also fails on 0.10

image

I did notice that it doesn't matter if I access either book.authors or book.authors_field... both of them seem to return the UnmanagedRealmList. Only the backing field should do that since we are overriding the get() to return the ManagedRealmList. So it looks like a bug in IntelliJ to me.

It actually looks like all our properties have the same problem...e.g. if you call book.title.length it also returns 0.

cmelchior commented 2 years ago

The same problem is also in present in Android Studio Dolphin Canary 5.

I did find work-around though. If you switch to Java in the dropdown, you can manually call the getters, which will do the correct thing, e.g. book.getAuthors() instead of book.authors.

CarsonRedeye commented 2 years ago

I did try that, but didn't think to switch to Java. Nice

cmelchior commented 2 years ago

I can reproduce the same behavior with our JVMConsole in IntelliJ 2021.3.2.

What is otherwise interesting is that e.g. this small demo works as expected:

    var title: String = ""
        get() = "Foo"
}

fun main(args: Array<String>) {
    val obj = Example()
    println(obj.title)
}

This makes me wonder if we are doing something wrong in our Compiler Plugin.

Theory 1: In our compiler plugin we are overriding both getters and setters, but if you try the same thing manually, the backing field is actually removed. I wonder if having both a backing field and getters and setters are somehow breaking some invariant somewhere 🤔 .

Theory 2: We had problems with setting origin in the bytecode before in the compiler plugin...maybe we are somehow not setting up the correct bytecode which confuses the IDE.

cmelchior commented 2 years ago

After some more digging. I now suspect it is a problem in our generated code somehow:

class Author : RealmObject {
    var firstName: String? = ""
    var lastName: String? = ""
    var age: Int? = 0

    @Ignore
    var ignoreMe: String = ""
        get() {
            return "Hello from Getter: ${field}"
        }
        set(value) {
            field = value
        }
}

In this class, the ignoreMe field works as expected when evaluating, while all the managed properties do not.

There is a few changes in the generated IR, especially this block is present in our generated accessors BLOCK type=kotlin.Nothing origin=null while it isn't in the default one....but not sure what it does nor if it even makes a difference.

rorbech commented 2 years ago

I managed to reproduce this also when:

I uploaded my experimental branch in https://github.com/realm/realm-kotlin/tree/cr/fix-debug-evaluation-of-realm-properties. It doesn't really included that many changes, but just to pin point exactly what I tried out.

I think this will be rather hard to trace from the bytecode side, so I would suggest doing a minimal compiler plugin that changes the getter and verify if the issue is there. From that we could either incrementally add more transformations until we can pin point the trigger or be able to raise the issue with IntelliJ if it is actually also showing up there.

clementetb commented 2 years ago

As Christian said in this comment, if we define a custom getter, the property gets evaluated correctly.

I checked the IR generated for a property with a default and a custom getter and there are no actual differences. It seems like a Kotlin debugger issue, it might be some optimization that displays the baking field and skips evaluating the accessor if there is no user defined one.

Have created this Jetbrains issue: https://youtrack.jetbrains.com/issue/IDEA-290618

There are two work arounds for this issue:

  1. Use the Java evaluator to call the accessors.
  2. Define a dummy getter on the properties you like to evaluate, like get() = ""