realm-java: OrderedRealmCollectionChangeListener issue when a change causes insertion or removal

Goal

Have RealmResults filtered by a field (isBoomarked, in my case), change value of the field to insert or remove objects.

Expected Results

Change isBookmarked value on a single object, and get a changeSet with a single deletionRange or insertionRange.

Actual Results

Sometimes a “random” changeRange is included in the changeSet.

Example

When isBookmarked is changed to false on single object, the changeSet usually comes with:

  • a deletionRange with the startIndex of the deleted object and length = 1 -> this is correct;
  • a changeRange with startIndex = 0 and length = 1 -> this seems like a bug, and it happens very often (not always). The removed object doesn’t have startIndex = 0.

Code (Kotlin)

where(Item::class.java).equalTo("isBookmarked", true).findAllSortedAsync("id", Sort.DESCENDING)
realm.executeTransaction { realm ->
    item.isBookmarked = !item.isBookmarked
}

Version of Realm and tooling

Realm version(s): 3.1.1

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 18 (10 by maintainers)

Commits related to this issue

Most upvoted comments

If an Item isBookmarked value is changed, should the Feed RealmChangeListener be triggered?

Do you mean the RealmChangeListener on the feed? In this case no, it won’t be triggered. If the RealmObject has a RealmList field, the listener on the RealmObject will be triggered when you remove/add element to the RealmList, but it will NOT be triggered if any list’s element’s field changes.

I’m not sure if that’s by design, and if I should be listening for changes on RealmList<Item> items instead.

But if the listener is on the field RealmList<Item> items, the listener should be triggered if any item in the items list isBookMarked change.

Let me know if it is till confusing you 😃

@jpmcosta Thanks a lot for the simple and clear demo project. I think it is very much like a bug.

First, to clarify the behaviour:

Yeah … we should probably doc them both in both places…

So the right behaviour would be if you change one Item, the change range should include all other elements. It actually works if you click on the first item at the first time, it will log:

04-13 08:26:30.296 16325-16325/com.jpmcosta.test.realmtestproject I/ItemListAdapter: deletion: 0, 1 04-13 08:26:30.296 16325-16325/com.jpmcosta.test.realmtestproject I/ItemListAdapter: change: 0, 9

But if click on others, the change range will only include only one element.

To reproduce this in a realm test case:

    @Test
    @RunTestInLooperThread
    public void allParentObjectShouldBeInChangeSet() {
        Realm realm = looperThread.realm;

        realm.beginTransaction();
        Owner owner = realm.createObject(Owner.class);
        Dog dog1 = realm.createObject(Dog.class);
        dog1.setHasTail(true);
        dog1.setOwner(owner);
        owner.getDogs().add(dog1);
        Dog dog2 = realm.createObject(Dog.class);
        dog2.setOwner(owner);
        dog2.setHasTail(true);
        owner.getDogs().add(dog2);
        Dog dog3 = realm.createObject(Dog.class);
        dog3.setOwner(owner);
        dog3.setHasTail(true);
        owner.getDogs().add(dog3);

        realm.commitTransaction();

        RealmResults<Dog> dogs = realm.where(Dog.class).equalTo(Dog.FIELD_HAS_TAIL, true).findAll();
        looperThread.keepStrongReference.add(dogs);
        dogs.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Dog>>() {
            @Override
            public void onChange(RealmResults<Dog> collection, OrderedCollectionChangeSet changeSet) {
                assertEquals(1, changeSet.getDeletions().length);
                assertEquals(0, changeSet.getInsertions().length);

                //int[] changes = changeSet.getChanges();
                assertEquals(1, changeSet.getChangeRanges().length);
                assertEquals(0, changeSet.getChangeRanges()[0].startIndex);
                assertEquals(2, changeSet.getChangeRanges()[0].length);

                looperThread.testComplete();
            }
        });

        realm.beginTransaction();
        //dog1.setHasTail(false); This would pass
        dog3.setHasTail(false);
        realm.commitTransaction();
    }

The issue is indeed related to parentFeed. To reproduce the issue:

@beeender I assume he would have to retain the reference to firstItem as field ref