Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable listeners on RealmList #4216

Merged
merged 12 commits into from
Feb 24, 2017
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

* Added support for sorting by link's field (#672).
* Added `OrderedRealmCollectionSnapshot` class and `OrderedRealmCollection.createSnapshot()` method. `OrderedRealmCollectionSnapshot` is useful when changing `RealmResults` or `RealmList` in simple loops.
* Added support for adding listeners on `RealmList`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens with the iterators?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RealmList's iterators stay the same. They have quite different meanings compared with RealmResults. e.g.: RealmList is actually mutable for users, but RealmResults is not.


### Internal

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@

package io.realm;

import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import io.realm.entities.AllTypes;
import io.realm.entities.Dog;
import io.realm.entities.Owner;
import io.realm.rule.RunInLooperThread;
import io.realm.rule.RunTestInLooperThread;
import io.realm.rule.TestRealmConfigurationFactory;
Expand All @@ -38,16 +40,34 @@
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertArrayEquals;

// Tests for the ordered collection fine grained notifications.
// This should be expanded to test the notifications for RealmList as well in the future.
@RunWith(AndroidJUnit4.class)
// Tests for the ordered collection fine grained notifications for both RealmResults and RealmList.
@RunWith(Parameterized.class)
public class OrderedCollectionChangeSetTests {

private enum ObservablesType {
REALM_RESULTS, REALM_LIST
}

private interface ChangesCheck {
void check(OrderedCollectionChangeSet changeSet);
}

@Rule
public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory();
@Rule
public final RunInLooperThread looperThread = new RunInLooperThread();

private final ObservablesType type;

@Parameterized.Parameters(name = "{0}")
public static List<ObservablesType> data() {
return Arrays.asList(ObservablesType.values());
}

public OrderedCollectionChangeSetTests(ObservablesType type) {
this.type = type;
}

@Before
public void setUp() {
}
Expand All @@ -57,9 +77,17 @@ public void tearDown() {
}

private void populateData(Realm realm, int testSize) {
Owner owner = null;
realm.beginTransaction();
if (type == ObservablesType.REALM_LIST) {
owner = realm.createObject(Owner.class);
}
for (int i = 0; i < testSize; i++) {
realm.createObject(AllTypes.class).setColumnLong(i);
Dog dog = realm.createObject(Dog.class);
dog.setAge(i);
if (type == ObservablesType.REALM_LIST) {
owner.getDogs().add(dog);
}
}
realm.commitTransaction();
}
Expand All @@ -82,26 +110,71 @@ private void checkRanges(OrderedCollectionChangeSet.Range[] ranges, int... index
}
}

// Deletes AllTypes objects which's columnLong is in the indices array.
// Re-adds the dogs so they would be sorted by age in the list.
private void reorderRealmList(Realm realm) {
RealmResults<Dog> dogs = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE);
Owner owner = realm.where(Owner.class).findFirst();
owner.getDogs().clear();
for (Dog dog : dogs) {
owner.getDogs().add(dog);
}
}

// Deletes Dogs objects which's columnLong is in the indices array.
private void deleteObjects(Realm realm, int... indices) {
for (int index : indices) {
realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, index).findFirst().deleteFromRealm();
realm.where(Dog.class).equalTo(Dog.FIELD_AGE, index).findFirst().deleteFromRealm();
}
}

// Creates AllTypes objects with columnLong set to the value elements in indices array.
// Creates Dogs objects with columnLong set to the value elements in indices array.
private void createObjects(Realm realm, int... indices) {
for (int index : indices) {
realm.createObject(AllTypes.class).setColumnLong(index);
realm.createObject(Dog.class).setAge(index);
}
if (type == ObservablesType.REALM_LIST) {
reorderRealmList(realm);
}
}

// Modifies AllTypes objects which's columnLong is in the indices array.
// Modifies Dogs objects which's columnLong is in the indices array.
private void modifyObjects(Realm realm, int... indices) {
for (int index : indices) {
AllTypes obj = realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, index).findFirst();
Dog obj = realm.where(Dog.class).equalTo(Dog.FIELD_AGE, index).findFirst();
assertNotNull(obj);
obj.setColumnString("modified");
obj.setName("modified");
}
}

private void moveObjects(Realm realm, int originAge, int newAge) {
realm.where(Dog.class).equalTo(Dog.FIELD_AGE, originAge).findFirst().setAge(newAge);
if (type == ObservablesType.REALM_LIST) {
reorderRealmList(realm);
}
}

private void registerCheckListener(Realm realm, final ChangesCheck changesCheck) {
switch (type) {
case REALM_RESULTS:
RealmResults<Dog> results = realm.where(Dog.class).findAllSorted(Dog.FIELD_AGE);
looperThread.keepStrongReference.add(results);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Dog>>() {
@Override
public void onChange(RealmResults<Dog> collection, OrderedCollectionChangeSet changeSet) {
changesCheck.check(changeSet);
}
});
break;
case REALM_LIST:
RealmList<Dog> list = realm.where(Owner.class).findFirst().getDogs();
looperThread.keepStrongReference.add(list);
list.addChangeListener(new OrderedRealmCollectionChangeListener<RealmList<Dog>>() {
@Override
public void onChange(RealmList<Dog> collection, OrderedCollectionChangeSet changeSet) {
changesCheck.check(changeSet);
}
});
break;
}
}

Expand All @@ -110,10 +183,10 @@ private void modifyObjects(Realm realm, int... indices) {
public void deletion() {
Realm realm = looperThread.realm;
populateData(realm, 10);
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<AllTypes>>() {

final ChangesCheck changesCheck = new ChangesCheck() {
@Override
public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeSet changeSet) {
public void check(OrderedCollectionChangeSet changeSet) {
checkRanges(changeSet.getDeletionRanges(),
0, 1,
2, 3,
Expand All @@ -125,7 +198,9 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
assertEquals(0, changeSet.getInsertions().length);
looperThread.testComplete();
}
});
};

registerCheckListener(realm, changesCheck);

realm.beginTransaction();
deleteObjects(realm,
Expand All @@ -139,13 +214,14 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
@RunTestInLooperThread
public void insertion() {
Realm realm = looperThread.realm;
populateData(realm, 0); // We need to create the owner.
realm.beginTransaction();
createObjects(realm, 0, 2, 5, 6, 7, 9);
realm.commitTransaction();
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<AllTypes>>() {

ChangesCheck changesCheck = new ChangesCheck() {
@Override
public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeSet changeSet) {
public void check(OrderedCollectionChangeSet changeSet) {
checkRanges(changeSet.getInsertionRanges(),
1, 1,
3, 2,
Expand All @@ -157,7 +233,8 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
assertEquals(0, changeSet.getDeletions().length);
looperThread.testComplete();
}
});
};
registerCheckListener(realm, changesCheck);

realm.beginTransaction();
createObjects(realm,
Expand All @@ -172,10 +249,9 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
public void changes() {
Realm realm = looperThread.realm;
populateData(realm, 10);
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<AllTypes>>() {
ChangesCheck changesCheck = new ChangesCheck() {
@Override
public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeSet changeSet) {
public void check(OrderedCollectionChangeSet changeSet) {
checkRanges(changeSet.getChangeRanges(),
0, 1,
2, 3,
Expand All @@ -187,7 +263,9 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
assertEquals(0, changeSet.getDeletions().length);
looperThread.testComplete();
}
});
};

registerCheckListener(realm, changesCheck);

realm.beginTransaction();
modifyObjects(realm,
Expand All @@ -202,10 +280,9 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
public void moves() {
Realm realm = looperThread.realm;
populateData(realm, 10);
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<AllTypes>>() {
ChangesCheck changesCheck = new ChangesCheck() {
@Override
public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeSet changeSet) {
public void check(OrderedCollectionChangeSet changeSet) {
checkRanges(changeSet.getDeletionRanges(),
0, 1,
9, 1);
Expand All @@ -218,10 +295,12 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
assertEquals(0, changeSet.getChanges().length);
looperThread.testComplete();
}
});
};
registerCheckListener(realm, changesCheck);

realm.beginTransaction();
realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 0).findFirst().setColumnLong(10);
realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 9).findFirst().setColumnLong(0);
moveObjects(realm, 0, 10);
moveObjects(realm, 9, 0);
realm.commitTransaction();
}

Expand All @@ -230,10 +309,9 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
public void mixed_changes() {
Realm realm = looperThread.realm;
populateData(realm, 10);
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<AllTypes>>() {
ChangesCheck changesCheck = new ChangesCheck() {
@Override
public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeSet changeSet) {
public void check(OrderedCollectionChangeSet changeSet) {
checkRanges(changeSet.getDeletionRanges(),
0, 2,
5, 1);
Expand All @@ -251,7 +329,9 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS

looperThread.testComplete();
}
});
};

registerCheckListener(realm, changesCheck);

realm.beginTransaction();
createObjects(realm, 11, 12, -1, -2);
Expand All @@ -269,10 +349,9 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
public void changes_then_delete() {
Realm realm = looperThread.realm;
populateData(realm, 10);
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<AllTypes>>() {
ChangesCheck changesCheck = new ChangesCheck() {
@Override
public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeSet changeSet) {
public void check(OrderedCollectionChangeSet changeSet) {
checkRanges(changeSet.getDeletionRanges(),
0, 2,
5, 1);
Expand All @@ -285,7 +364,8 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS

looperThread.testComplete();
}
});
};
registerCheckListener(realm, changesCheck);

realm.beginTransaction();
modifyObjects(realm, 0, 1, 5);
Expand All @@ -299,13 +379,14 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
public void insert_then_delete() {
Realm realm = looperThread.realm;
populateData(realm, 10);
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllSorted(AllTypes.FIELD_LONG);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<AllTypes>>() {
ChangesCheck changesCheck = new ChangesCheck() {
@Override
public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeSet changeSet) {
public void check(OrderedCollectionChangeSet changeSet) {
fail("The listener should not be triggered since the collection has no changes compared with before.");
}
});
};

registerCheckListener(realm, changesCheck);

looperThread.postRunnableDelayed(new Runnable() {
@Override
Expand All @@ -324,12 +405,16 @@ public void run() {
@Test
@RunTestInLooperThread
public void emptyChangeSet_findAllAsync(){
if (type == ObservablesType.REALM_LIST) {
looperThread.testComplete();
return;
}
Realm realm = looperThread.realm;
populateData(realm, 10);
final RealmResults<AllTypes> results = realm.where(AllTypes.class).findAllSortedAsync(AllTypes.FIELD_LONG);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<AllTypes>>() {
final RealmResults<Dog> results = realm.where(Dog.class).findAllSortedAsync(Dog.FIELD_AGE);
results.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Dog>>() {
@Override
public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeSet changeSet) {
public void onChange(RealmResults<Dog> collection, OrderedCollectionChangeSet changeSet) {
assertSame(collection, results);
assertEquals(9, collection.size());
assertNull(changeSet);
Expand All @@ -338,15 +423,15 @@ public void onChange(RealmResults<AllTypes> collection, OrderedCollectionChangeS
});

final CountDownLatch bgDeletionLatch = new CountDownLatch(1);
// beginTransaction() will make the async query return immediately. So we have to delete an object in another
// beginTransaction() will make the async query return immediately. So we have to create an object in another
// thread. Also, the latch has to be counted down after transaction committed so the async query results can
// contain the modification in the background transaction.
new Thread(new Runnable() {
@Override
public void run() {
Realm realm = Realm.getInstance(looperThread.realmConfiguration) ;
realm.beginTransaction();
realm.where(AllTypes.class).equalTo(AllTypes.FIELD_LONG, 0).findFirst().deleteFromRealm();
realm.where(Dog.class).equalTo(Dog.FIELD_AGE, 0).findFirst().deleteFromRealm();
realm.commitTransaction();
realm.close();
bgDeletionLatch.countDown();
Expand Down
Loading