realm-kotlin icon indicating copy to clipboard operation
realm-kotlin copied to clipboard

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

Open CarsonRedeye opened this issue 2 years ago • 12 comments

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.

CarsonRedeye avatar Mar 04 '22 03:03 CarsonRedeye

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

nhachicha avatar Mar 04 '22 05:03 nhachicha

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

CarsonRedeye avatar Mar 04 '22 05:03 CarsonRedeye

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?

nhachicha avatar Mar 04 '22 05:03 nhachicha

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

CarsonRedeye avatar Mar 04 '22 05:03 CarsonRedeye

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 avatar Mar 04 '22 08:03 cmelchior

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 avatar Mar 04 '22 08:03 cmelchior

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.

cmelchior avatar Mar 04 '22 09:03 cmelchior

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

CarsonRedeye avatar Mar 04 '22 09:03 CarsonRedeye

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 avatar Mar 04 '22 10:03 cmelchior

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.

cmelchior avatar Mar 04 '22 11:03 cmelchior

I managed to reproduce this also when:

  • not explicitly setting an origin as introduced in #386
  • modifying the IR to just return a string constant in the getter to ensure that it wasn't some other logic causing this

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.

rorbech avatar Mar 14 '22 12:03 rorbech

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() = ""

clementetb avatar Mar 17 '22 11:03 clementetb