0

Long story short: I do not know why my RealmChangeListener does not trigger as intended under certain circumstances and am looking for advice on why a RealmChangeListener could not be working.

Update: I have verified that the RealmResults stay valid = true; loaded=false. If I execute RealmResults.load() after adding the change listener, it will output a vague Throwing Exception 7 in the log and a BadVersionException when I step through the Realm source. I think this exception makes some sense, the asynchronous write updated the Realm and therefore the query seems to no longer work. However, both the executeTransactionAsync that writes in the MainActivity as well as the asynchronous queries are started from the main thread.

-

I have a MainActivity, in which upon pressing a button an asynchronous write will be performed.

I have another button that opens a second activity that displays data.

The second activity uses a ViewPager with a fragment for each tab. Each tab has a different query.

So what happens now is the following: I open the second activity, which instantiates four fragments, not attaching them to the activity.

The activity then executes queries, passing each RealmResults to the fragment, in which a RealmChangeListener will be installed to display the data once it is loaded. Could it be that the RealmChangeListener does not work when the fragment is not attached to an Activity?

Anyway, this is the method in the fragment that receives the RealmResults (created by findAllAsyncSorted()) and is supposed to update the data on the Adapter:

public void updateData(OrderedRealmCollection<Bean> realmCollection) {
        Timber.v("Delegated data to fragment of adapter %s.", adapter);
        this.data = (RealmResults<Bean>) realmCollection;

    if (data.isLoaded()) {
        Timber.d("Data is already loaded on adapter %s.", adapter);
        adapter.updateDataManual(data);
    }

    if (!data.isValid()) {
        Timber.e("Data is not valid.");
    }


    listener = new RealmChangeListener<RealmResults<Bean>>() {
        @Override public void onChange(RealmResults<Bean> newResults) {
            Timber.v("Change listener for manual data triggered: %d results in fragment for category %s and adapter %s.",
                newResults.size(), category.toString(), adapter);

            adapter.updateDataManual(newResults);
        }

        @Override protected void finalize() throws Throwable {
            Timber.d("Finalizing change listener for adapter %s.", adapter);
            super.finalize();
        }
    };

    data.addChangeListener(listener);

    MyTimer.setRepeatingCallback(() -> {

        Timber.v("RealmResults in adapter %s are %s and %s, and the ChangeListener is %s.",
            adapter,
            data.isValid() ? "valid" : "invalid",
            data.isLoaded() ? "loaded" : "not loaded",
            listener);

        return true;
    }, 5000);
}

As you can see, I made efforts to ensure that the query is valid and has not loaded until the change listener is added and that neither the RealmResults nor the RealmChangeListener are garbage collected.

Still, out of four RealmChangeListeners, only two or less (sometimes zero) trigger.

Note that this only happens if the second activity is opened shortly after starting the asynchronous write on the MainActivity. If I wait for 2 seconds, everything works as intended. I did verify that the RealmChangeListener is not garbage collected, since the finalize() is called after exiting the app. I have no idea what could prevent the listener from working. Is there anything specific I should pay attention to?

ferbeb
  • 163
  • 1
  • 2
  • 11
  • Your `data` object goes out of scope when the method finishes, so it is most likely being GC'ed. You need to save it in a class variable. – Christian Melchior Jan 23 '17 at 11:08
  • @ChristianMelchior that's also what I thought, but he has `this.data = (RealmResults) realmCollection;` which means the results should be saved as a field variable already - but if he swaps it out, then yes – EpicPandaForce Jan 23 '17 at 11:31
  • @ChristianMelchior Indeed I store it in a field which is only accessed from the posted method. Right now I'm trying to write a test class, hope I can reproduce the problem there. – ferbeb Jan 23 '17 at 11:55
  • @EpicPandaForce I narrowed the problem down to a `BadVersionException` when calling `load()` on the `RealmResults` directly after adding the change listener. The description of the exception makes perfect sense, namely the query not being usable against the more up-to-date state of the realm (after the async write has completed). Maybe it has something to do with using separate Realm instances. – ferbeb Jan 24 '17 at 08:14
  • I do not see a single case when calling `load()` is ever necessary. If you need the results immediately, then use the synchronous query. But good to know that manually calling `load()` can cause problems. – EpicPandaForce Jan 24 '17 at 08:19
  • @EpicPandaForce I added it just for debugging, because I found it really strange that the RealmResults stay in the state of loaded: false, valid: true and the ChangeListener still being present. I guess the `BadVersionException` is what prevents the ChangeListener from working on the asynchronous query, but it is only logged when calling `load()`. – ferbeb Jan 24 '17 at 08:22
  • Which Realm version are you using? – EpicPandaForce Jan 24 '17 at 08:32
  • @EpicPandaForce 2.3.0 – ferbeb Jan 24 '17 at 09:07
  • @ChristianMelchior I tried a little more and removed the (debug) `RealmResults.load()` and stepped through the `findAllSortedAsync(String fieldNames[], final Sort[] sortOrders)`. The weird thing is, everything seems to run without any error, `completeAsyncResults(QueryUpdateTask.Result result)` in `AndroidNotifier.java` is called. When I place a breakpoint on Line 122 where it sends this `COMPELTED_ASYNC_REALM_RESULTS` message, my bug is gone, the ChangeListener triggers. However, when I disable the suspend and only let the breakpoint log, it logs before the write completes and the bug happens – ferbeb Jan 25 '17 at 10:01
  • Apparently that was a mistake on my side. Somehow just suspending on `handler.obtainMessage()` no longer works everytime. I give up on this and hope that [#3834](https://github.com/realm/realm-java/pull/3834) fixes my issue by coincidence. Anyway, suspending on this line makes the ChangeListener work way more often than when the breakpoint is disabled. – ferbeb Jan 25 '17 at 10:23
  • 2
    This has been fixed in Realm Java 3.0.0 – Christian Melchior Mar 20 '17 at 12:14

1 Answers1

1

You need to have a (strong) field reference to the RealmResults<T> so that you keep it from getting GC'd.

If a RealmResults gets GC'd, then Realm can no longer auto-update it.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Indeed I store it in a field which is only accessed from the posted method. Right now I'm trying to write a test class, hope I can reproduce the problem there. – ferbeb Jan 23 '17 at 11:55
  • Also, if you close the Realm instance the result set belongs to, then RealmChangeListeners for Results that belong to that Realm instance will no longer be called, because the result set is invalidated. – EpicPandaForce Jan 23 '17 at 12:06
  • Hm, I know this is hard for you guys to debug remotely without seeing my code. Right now I added a timer that prints the following every five seconds: `Timber.v("RealmResults in adapter %s are %s, and the ChangeListener is %s.". adapter, data.isValid() ? "valid" : "invalid", listener);` The RealmResults are valid and the listener is not garbage collected. I will see if I can reproduce it in an instrumented test, a first try did not reproduce the problem, sadly. – ferbeb Jan 23 '17 at 15:45