Realm-java: isLoaded() seems to be returning an incorrect value.

Created on 20 Jul 2016  路  29Comments  路  Source: realm/realm-java

I have created a project to replicate the issue I am having. https://github.com/chrisolsen/realm-issue

In the onChange listener the RealmObject's isLoaded() method is returning a true value, but when I then try to bind the data an IllegalStateException is thrown telling me that I should first check it is loaded.

If you run the linked android project the bug will only be seen on the first launch, after that a row will exist in the db thereby avoiding the issue.

Expected Results

isLoaded should initially return false, until the IntentService inserts the data.

Actual Results

isLoaded() returns true before any data exists in the db, causing a crash

07-20 10:14:46.833 5112-5112/com.example.chris.test D/User null?: no
07-20 10:14:46.833 5112-5112/com.example.chris.test D/User loaded?: true
07-20 10:14:46.836 5112-5262/com.example.chris.test D/onHandleIntent: in the service
07-20 10:14:46.863 5112-5112/com.example.chris.test D/AndroidRuntime: Shutting down VM


                                                                      --------- beginning of crash
07-20 10:14:46.863 5112-5112/com.example.chris.test E/AndroidRuntime: FATAL EXCEPTION: main
                                                                      Process: com.example.chris.test, PID: 5112
                                                                      java.lang.IllegalStateException: Can't access a row that hasn't been loaded, make sure the instance is loaded by calling RealmObject.isLoaded().
                                                                          at io.realm.internal.Row$1.getString(Row.java:176)
                                                                          at io.realm.UserRealmProxy.realmGet$name(UserRealmProxy.java:60)
                                                                          at com.example.chris.test.UserData.getName(UserData.java:11)
                                                                          at com.example.chris.test.databinding.ActivityMainBinding.executeBindings(ActivityMainBinding.java:96)
                                                                          at android.databinding.ViewDataBinding.executePendingBindings(ViewDataBinding.java:355)
                                                                          at android.databinding.ViewDataBinding$6.run(ViewDataBinding.java:172)
                                                                          at android.databinding.ViewDataBinding$7.doFrame(ViewDataBinding.java:238)
                                                                          at android.view.Choreographer$CallbackRecord.run(Choreographer.java:856)
                                                                          at android.view.Choreographer.doCallbacks(Choreographer.java:670)
                                                                          at android.view.Choreographer.doFrame(Choreographer.java:603)
                                                                          at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
                                                                          at android.os.Handler.handleCallback(Handler.java:739)
                                                                          at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                          at android.os.Looper.loop(Looper.java:148)
                                                                          at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                                          at java.lang.reflect.Method.invoke(Native Method)
                                                                          at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                                          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Steps & Code to Reproduce

Run the linked app. The first time the app is run the above crash will occur, after that the app will work as intended.

Code Sample

See https://github.com/chrisolsen/realm-issue

Version of Realm and tooling

Realm version(s): 1.1.0

Android Studio version: 2.1.2

Which Android version and device: Nexus5 emulator (android 6.0), Nexus 7 device (android 6.0)

T-Bug

Most helpful comment

Yes, that sounds plausible. You can use OrderedcollectionChangeSet.isCompleResult(). That will return true once you have downloaded the results from the server.

All 29 comments

Thanks a lot for the demo project, it makes the issue very clear!

The exception is not totally correct, and it is very misleading. Really sorry for it.

There is a bit misunderstanding of the isLoaded(), when it returns true, it means the query is finished. So if the asyc query finishes before the transaction in the intent service, you will get the listener triggered with isLoaded() returns true, but it is an empty RealmObject (since no object match the query condition).
In this case, instead of calling isLoaded() just call isValid() in your listener.

If the async query returns a empty RealmObject, the same query will rerun when transaction happens. So the only thing you need to change is using isValid() instead.

We will modify the exception message soon.

And the javadoc for isLoaded() is not correct which needs to be fixed as well.

Also, isValid() could return false if the object gets deleted. So maybe you need:

if (obj.isLoaded()) {
    if (obj.isValid()) {
    // Query is finished and object is available
    } else {
    // Query is finished but the object gets deleted.
    }
}

Things now work. Thanks so much for the help!

If an issue already exists for this bug feel free to close this one.

Can user.isLoaded() ever return false once we are in the RealmChangeListener callback?

@didot Good point! If the RealmChangeListener is registered on the corresponding RealmObject the isLoaded should always return true when the listener called. But it could return false if the listener is registered on something else.

Fixed by #3240

i am creating query for query-based realm and attach change listener, but isLoaded() is always true, first time onChange() called with empty realmResult(isLoaded() is true) second time with realmResultthat matching the query(isLoaded() is true), maybe i am not understand something? i need to return callback onSuccess(result)/onError() but i have no indication when the query is done

@pavelpoley Please provide some code example. It isn't 100% clear from your description exactly what could be wrong

i call this method, onChangecalled 2 time, first time with empty realmResults, second time with correct result that matching the query, but isLoaded() in both cases are true.

I am using repository class to encapsulate some async requests and i want close resources realm/listeners automatically when the query is end

`Realm.getInstanceAsync(getSharedConfiguration(), new Realm.Callback() {
            @Override
            public void onSuccess(@NonNull Realm realm) {

                RealmQuery<RealmUser> query = realm.where(RealmUser.class);

                for (int i = 0; i < ids.length; i++) {
                    String id = ids[i];
                    if (i < ids.length - 1) {
                        query.equalTo("id", id).or();
                    } else {
                        query.equalTo("id", id);
                    }
                }

                RealmResults<RealmUser> realmResults = query.findAllAsync();

                realmResults.addChangeListener(new RealmChangeListener<RealmResults<RealmUser>>() {

                    @Override
                    public void onChange(@NonNull RealmResults<RealmUser> realmUsers) {

                        if (realmUsers.isLoaded()){

                            List<RealmUser> users = realm.copyFromRealm(realmUsers);

                            //realmResults.removeAllChangeListeners();

                            //realm.close();

                            callback.onSuccess(users);

                        }

                    }
                });

            }

            @Override
            public void onError(@NonNull Throwable exception) {
                callback.onError(exception.getLocalizedMessage());
            }
        });`

Hmm, yes, that doesn't sound right. I'll investigate

i am using 5.9.1 realm version

Your RealmResults can die if it's not stored somewhere in a field.

Store it globally? even i want to use and close inside the method?

I recommend using an IdentitySet<? extends OrderedRealmCollection<?>> field into which you place the RealmResults, then if you want to remove it from there, then remove it.

Please note that IdentitySet is in io.realm.internal, but technically what it does works and you can copy it.

See https://github.com/realm/realm-java/blob/0e1464114782f88165309bd0d73d13a8fde72ab7/realm/realm-library/src/main/java/io/realm/internal/IdentitySet.java#L25-L31

any news about isLoaded(), may i am doing something wrong?

any news or alternative how to get final updated data so Realm instance will safely closed?

I just ran the following test:

    @Test
    @RunTestInLooperThread
    public void isLoaded_returnsFalseTheFirstTime() {
        final Realm realm = looperThread.getRealm();
        populateTestRealm(realm, 10);
        RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllAsync();
        assertFalse(results.isLoaded());
        assertEquals(0, results.size());
        results.addChangeListener(objects -> {
            assertTrue(objects.isLoaded());
            assertEquals(10, objects.size());
            looperThread.testComplete();
        });
        looperThread.keepStrongReference(results);
    }

Which parses fine. Are you perhaps writing to the Realm in the background, so the first time the query completes it finds nothing, but then updates with the full result?

I am using query based synchronization, maybe first result is local result, and second result fetched from server, but isLoaded() is true in both cases, if this is expected behavior so i how i get the updated result from server?

Yes, that sounds plausible. You can use OrderedcollectionChangeSet.isCompleResult(). That will return true once you have downloaded the results from the server.

@cmelchior oh thanks! it is work now! the is not enough information about OrderedcollectionChangeSet in docs!

Ah yes. Knowing if it's local, full sync or query based sync is important :+1:

Can you clarify, isLoaded() work only for local Realm or for full sync too?

I assume the trick is that isLoaded means that the async query has fetched data, but it doesn't actually guarantee that Realm has already synchronized all data from the ROS.

isLoaded() works for both local and synced Realms, but it just indicates that the local query completed. It doesn't tell you anything about objects on the server. So this is normally what happens.

1) Start local query
2) Subscribe to objects (happens automatically if using findAlllASync()
3) Local query completes and invokes changelistener: isLoaded() == true, isCompleteResult() == false, size() == 0
4) The client finishes downloading the results from the server.
5) Changelistener is invoved again with isLoaded() == true, isCompleteResult() == true, size() == 42

Basically, changes from the server are integrated into the Realm just like if you wrote the data yourself starting from an empty Realm.

So if for query based Realm i have OrderedCollectionChangeSet that give indication if all data fetched, what i have for full sync Realm to know if onChangecalls with updated data? (in case i don't have open realm in activity/fragment , and it encapsulated in function - and all data may not have synchronized) and i want to return actual result from server, for example pm.getPermissions(new PermissionManager.PermissionsCallback()), __permission is full sync Realm as i see in Realm studio

The definition of all data fetched is a bit fuzzy, since Realm can work with multiple versions of your data at the time.

For query-based Realms, using isCompleteResult() will make sure that you downloaded data you asked for.

For both query-based Realms and fully synchronized Realms you can register a progress listener on the SyncSession. That can tell you if there is data on the server you can download.

There is a function called SyncSession.downloadAllRemoteServerChanges() that you can use to make sure you have all the latest data.

SyncSession.downloadAllRemoteServerChanges() will fetch the system files too? like __permission? i see that configuration required

No, those objects belongs to different Realms so you need to call that method for all those Realms, but they are not directly exposed: https://github.com/realm/realm-java/blob/master/realm/realm-library/src/objectServer/java/io/realm/PermissionManager.java#L181

Was this page helpful?
0 / 5 - 0 ratings