0

I'm trying to use GreenRobot to share RealmResults between fragments. When the user clicks certain buttons changing the data I want to show, I call invalidateRealmResult:

public void invalidateRealmResult() {
    RealmQuery<Climb> realmQuery =  mRealm.where(Climb.class);
    // other query modifiers added here....

    mResult = realmQuery.findAllSorted("date", Sort.ASCENDING);
    RealmChangeListener listener = new RealmChangeListener<RealmResults<Climb>>() {
            @Override
        public void onChange(RealmResults<Climb> element) {
            Log.d(TAG, "Realmresult onchange");
            EventBus.getDefault().postSticky(new RealmResultsEvent(mResult));
        }
    };
    mResult.addChangeListener(listener);
    EventBus.getDefault().postSticky(new RealmResultsEvent(mResult));
}

My greenrobot event looks like this:

public class RealmResultsEvent {
    public RealmResult mResult;

    public RealmResultsEvent(RealmResult result){
        this.mResult = result
    }
}

Any fragment that needs to update with the new result subscribes to the event:

@Subscribe(sticky = true)
public void onRealmResultEvent(RealmResultsEvent event) {
    mResult = event.mResult;
    updateView();
}

@Override
public void onStop() {
    super.onStop();
    EventBus.getDefault().unregister(this);
}

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

I've made sure that all fragments subscribe in onStart() and unsubscribe in onStop().

My issue is that new event posts don't appear to overwrite the old RealmResults. I can tell that the RealmResults is not being destroyed because the log message I have in the RealmChangeListener gets called an increasing amount whenever I add to the realm database, and every once in a while the UI shows the wrong query results.

05-13 10:36:55.523 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.536 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.544 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.551 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.558 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.566 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.574 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.583 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.591 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.600 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.608 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange
05-13 10:36:55.616 17684-17684/com.example.grant.wearableclimbtracker D/MainActivity: Realmresult onchange

Every time I make a change, it calls the listener an alternating 3, 6, 12, 6, 3... times, repeating that pattern. Not sure what that means or if that information helps anyone.

G. Blake Meike
  • 6,615
  • 3
  • 24
  • 40
chanban
  • 480
  • 1
  • 4
  • 19
  • Hey @grantka, I'm not sure I understand the question. Are there two different variables called `mResults`? Where is the code that changes its contents, thereby firing the `Listener`? – G. Blake Meike May 16 '17 at 18:12
  • Hi @G.BlakeMeike, yes there is an mResult in my MainActivity and an mResult in the fragments that contain the UI elements displaying the data. I didn't show the code that fires the listener, but it's basically just adding Climb objects to the realm – chanban Jun 01 '17 at 08:07

2 Answers2

1

From the sample snippets provided in the question it's hard to tell why the previous mResult isn't being getting GC'd each time invalidateRealmResult() is called. I agree with your assessment, that it appears not to be GC'd, since the listener is still around responding. My guess is that there is an extra reference to mResult that we're missing here.

General Realm Use:

One observation from looking at the samples. It's not clear, why you need to re-run the query each time a Climb result is added to the database. Realm results are live, meaning that if the underlying data matching the query result changes, the data is automatically updated. No need to re-query. Adding a change listener, gives you a way to subscribe to that event (that the results data has changed) and react in your UI. You might be able to simplify things and avoid this GC issue, by adjusting your code to something like the following:

@Override
public void onStop() {
    super.onStop();
}

@Override
public void onStart() {
    super.onStart();
    mResult = fetchRealmResults()
}

public void fetchRealmResults() {
    RealmQuery<Climb> realmQuery =  mRealm.where(Climb.class);
    // other query modifiers added here....

    // Async suffix is optional here, but will push 
    // the work to a worker thread and then come back 
    // with the results on the main thread.
    mResult = realmQuery.findAllSortedAsync("date", Sort.ASCENDING);
    RealmChangeListener listener = new 
    RealmChangeListener<RealmResults<Climb>>() {
        @Override
        public void onChange(RealmResults<Climb> element) {
            // This will fire anytime there is a change 
            // to the underlying database that affects 
            // the results of this query.
            Log.d(TAG, "Realmresult onchange");
            updateView();
        }
    };
    mResult.addChangeListener(listener);
}

Note I took out the GreenRobot stuff for simplicity, but you could continue to use it. I would only use it to trigger the event to your fragments, but not send the updated results, because your fragments already have the updated results in their existing mResult reference. Make sense?

  • While I love this answer, I have to point out that relying on the GC is like depending on the weather. I would assert that any algorithm that depends on something being GCed is fundamentally unsound. – G. Blake Meike May 16 '17 at 21:53
  • @Eric Maxwell Makes sense, I will try this out and see if it makes a difference. – chanban Jun 01 '17 at 08:08
0

I managed to remove chunks of code until I narrowed down on the issue. It has nothing to do with eventbus. For some reason, when I reassign realmresult to a new query result, it doesn't garbage collect the old realmresult and the changelisteners for the old results were still firing. This created a race condition and whichever listener finished last would show up on my UI.

I pseudo fixed this by removing the listeners before reassigning the field:

public void invalidateRealmResult() { 
    RealmQuery<Climb> realmQuery =  mRealm.where(Climb.class);
    // other query modifiers added here.... 

    // ADDED THIS TO AVOID CALLING MULTIPLE LISTENERS
    if(mResult != null) {
        mResult.removeAllChangeListeners();
    }
    mResult = realmQuery.findAllSorted("date", Sort.ASCENDING);
    RealmChangeListener listener = new RealmChangeListener<RealmResults<Climb>>() {
        @Override 
        public void onChange(RealmResults<Climb> element) {
            Log.d(TAG, "Realmresult onchange");
            EventBus.getDefault().postSticky(new RealmResultsEvent(mResult)); 
        } 
    }; 
    mResult.addChangeListener(listener);
    EventBus.getDefault().postSticky(new RealmResultsEvent(mResult)); 
}

I call this a pseudo fix because it no exhibits the strange behavior, but I'm pretty sure there are still RealmResult objects that aren't being GC'd and I'm not sure why.

chanban
  • 480
  • 1
  • 4
  • 19
  • I think instead of relying on the GC to remove listeners, it is better to just call `RealmResults.removeChangeListener(listener)` to stop notification delivery. – beeender May 17 '17 at 04:11
  • @beeender Isn't that what the `mResult.removallAllChangeListeners()` does? In this example, when I'm removing the listener I no longer have access to the local variable `listener`. I could make it a field variable, but why do that when I can blanket remove all listeners. – chanban Jun 01 '17 at 08:00
  • uh, yes, `mResult.removeAllChangeListeners()` does it. The key thing is GC won't happen immediately after your `mResults` gets reassigned. – beeender Jun 02 '17 at 08:41