No Change Event or something.
Change Event occured and it has wrong ChangeSet
public class RealmPerson extends RealmObject {
@Index
private int id;
@Required
private String name;
}
2.Add initial items and subscribe change event
realm.beginTransaction();
try {
realm.delete(RealmPerson.class);
for (int i = 0; i < 10000; i++) {
RealmPerson test = new RealmPerson();
test.setId(i);
test.setName("name" + i);
realm.copyToRealm(test);
}
realm.commitTransaction();
} catch (Throwable t) {
realm.cancelTransaction();
throw t;
}
partialPesons = realm.where(RealmPerson.class).greaterThanOrEqualTo("id", 8000).findAll();
partialPesons.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<RealmPerson>>() {
@Override
public void onChange(RealmResults<RealmPerson> collection, OrderedCollectionChangeSet changeSet) {
Log.i("partialPersons", "ttt");
dumpChangSet(changeSet);
}
});
and dumpChangeSet is something like the followings
private static void dumpChangSet(OrderedCollectionChangeSet changeSet) {
for (int index : changeSet.getDeletions())
Log.i("deletion", String.valueOf(index));
for (int index : changeSet.getInsertions())
Log.i("insertion", String.valueOf(index));
for (int index : changeSet.getChanges())
Log.i("modification", String.valueOf(index));
}
3.Delete items
realm.beginTransaction();
try {
RealmResults<RealmPerson> targets = realm.where(RealmPerson.class).equalTo("id", 3000).findAll();
targets.deleteAllFromRealm();
realm.commitTransaction();
} catch (Throwable t) {
realm.cancelTransaction();
throw t;
}
4.
I deleted item with ID of 3000 that partialPesons does'nt contain
but change event occured and it's ChageSet has wrong index.
you are releaseing new functinon without basic test??
Realm version(s): 3.1.1
Realm sync feature enabled: no
Android Studio version: 2.3
Which Android version and device: ?
Your results is built from:
partialPesons = realm.where(RealmPerson.class).greaterThanOrEqualTo("id", 8000).findAll();
which includes all RealmPerson whose id is >= 8000
but what you deleted is id == 3000 which is not a part of the results.
And I think that is why the listener is not called.
it's reverse.
Actual result is that the linster is called.
Expected Results is that change event does not occur.
Sorry for my poor English.
OK, let me try that :)
I've just tried it (although it is fine if Beender also tries it) but
04-11 14:44:38.433 3127-3127/com.zhuinden.realmbreak I/MainActivity: DELETING [Aardwolf]
OrderedRealmChangeListener defined as
words = realm.where(Word.class).not().beginsWith(WordFields.WORD, "A").findAllSorted(WordFields.WORD, Sort.ASCENDING);
was not called
Delete happens locally
Single.just("").delay(5000, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(s -> {
if(realm != null) {
realm.executeTransaction(_realm -> {
_realm.where(Word.class).equalTo(WordFields.WORD, "Aardwolf").findAll().deleteAllFromRealm();
Log.i(TAG, "DELETING [Aardwolf]");
});
}
});
Changing condition to
words = realm.where(Word.class).not().beginsWith(WordFields.WORD, "B").findAllSorted(WordFields.WORD, Sort.ASCENDING);
And deleting
Single.just("").delay(5000, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(s -> {
if(realm != null) {
realm.executeTransaction(_realm -> {
_realm.where(Word.class).equalTo(WordFields.WORD, "Aang").findAll().deleteAllFromRealm();
Log.i(TAG, "DELETING [Aang]");
});
}
});
Returns log:
04-11 14:46:55.417 5066-5066/com.zhuinden.realmbreak I/MainActivity: DELETING [Aang]
04-11 14:46:55.445 5066-5066/com.zhuinden.realmbreak I/MainActivity: Deletion [0]
Aang was 0th index and it was called only when it was in the RealmResults.
My complete code is
public class MainActivity extends AppCompatActivity {
private Realm realm;
private RealmResults<RealmPerson> allPersons;
private RealmResults<RealmPerson> partialPersons;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Realm.init(this);
realm = Realm.getDefaultInstance();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_activity, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
return true;
}
private static void dumpChangSet(OrderedCollectionChangeSet changeSet) {
for (int index : changeSet.getDeletions())
Log.i("deletion", String.valueOf(index));
for (int index : changeSet.getInsertions())
Log.i("insertion", String.valueOf(index));
for (int index : changeSet.getChanges())
Log.i("modification", String.valueOf(index));
}
private void addItems() {
realm.beginTransaction();
try {
realm.delete(RealmPerson.class);
for (int i = 0; i < 10000; i++) {
RealmPerson test = new RealmPerson();
test.setId(i);
test.setName("name" + i);
realm.copyToRealm(test);
}
realm.commitTransaction();
} catch (Throwable t) {
realm.cancelTransaction();
throw t;
}
allPersons = realm.where(RealmPerson.class).findAll();
allPersons.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<RealmPerson>>() {
@Override
public void onChange(RealmResults<RealmPerson> collection, OrderedCollectionChangeSet changeSet) {
Log.i("allPersons", "ttt");
dumpChangSet(changeSet);
}
});
partialPersons = realm.where(RealmPerson.class).greaterThanOrEqualTo("id", 8000).findAll();
partialPersons.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<RealmPerson>>() {
@Override
public void onChange(RealmResults<RealmPerson> collection, OrderedCollectionChangeSet changeSet) {
Log.i("partialPersons", "ttt");
dumpChangSet(changeSet);
}
});
}
private void addItem() {
Log.i("add item", "ttt");
realm.beginTransaction();
try {
RealmPerson test = new RealmPerson();
test.setId(3000);
test.setName("name");
realm.copyToRealm(test);
realm.commitTransaction();
} catch (Throwable t) {
realm.cancelTransaction();
throw t;
}
}
private void updateItem() {
Log.i("update item", "ttt");
realm.beginTransaction();
try {
RealmResults<RealmPerson> targets = realm.where(RealmPerson.class).equalTo("id", 3000).findAll();
for (RealmPerson person : targets) {
person.setName("ABC");
}
realm.commitTransaction();
} catch (Throwable t) {
realm.cancelTransaction();
throw t;
}
}
private void deleteItem() {
Log.i("delete item", "ttt");
realm.beginTransaction();
try {
RealmResults<RealmPerson> targets = realm.where(RealmPerson.class).equalTo("id", 3000).findAll();
targets.deleteAllFromRealm();
realm.commitTransaction();
} catch (Throwable t) {
realm.cancelTransaction();
throw t;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add_items:
addItems();
return true;
case R.id.action_add_item:
addItem();
return true;
case R.id.action_update_item:
updateItem();
return true;
case R.id.action_delete_item:
deleteItem();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
Then when I select delete menu.
logs are
04-11 10:50:13.957 3960-3960/realmtest2 I/delete聽item: ttt
04-11 10:50:13.967 3960-3960/realmtest2 I/allPersons: ttt
04-11 10:50:13.967 3960-3960/realmtest2 I/deletion: 3000
04-11 10:50:13.967 3960-3960/realmtest2 I/deletion: 9999 <- ???
04-11 10:50:13.967 3960-3960/realmtest2 I/insertion: 3000 <- ???
04-11 10:50:13.967 3960-3960/realmtest2 I/partialPersons: ttt
04-11 10:50:13.967 3960-3960/realmtest2 I/deletion: 1999 <-- ????
04-11 10:50:13.967 3960-3960/realmtest2 I/insertion: 0 <-- ???
Insertion??
Hey, i can produce your issue, but, it is actually the expected behaviour :)
you can get the desired behavior if you change:
partialPersons = realm.where(RealmPerson.class).greaterThanOrEqualTo("id", 8000).findAll();
to findAllSorted("id").
This is is because of the order of returned results by findAll() is not guaranteed, it could be changed even no elements change in it.
When you delete the id=3000 RealmPerson, internally in realm core, the last element before whose id is 9999 will be moved to the empty slot where the id = 3000 is (to make it faster). Without sorting, findAll()'s results will update to the id = 9999 as the first element. That why you get an insertion at index 0.
So, just sort the results ~
oh so that's why my example was working, I used findAllSorted() instead of findAll().
Wow I'm terrible at reproducing bugs if I fix them without noticing 馃槄
oh. Thx a lot.
I understand what u told then one question comes to my mind.
How about Model's RealmResults??
Let's say I have the following models
public class RealmHouse extends RealmObject {
private RealmResults<RealmPerson> persons;
public RealmResults<RealmPerson> getPersons() {
return persons;
}
}
Then i also need to sort this like the following for the same behaviours?
RealmHouse house;
house.getPersons().sort("id").addChangeListener(..);
hmmm, that is a very good question!
Actually i am not quite sure about that. what is think is yes, you need to sort it otherwise it won't maintain the order, but i will try to confirm it.
one thing about your code above is:
RealmResults sorted = house.getPersons().sort("id");
sorted.addChangeListener(..);
you still need to maintain a strong ref to the sorted, otherwise after it gets GCed, your listener won't be triggered anymore.
you still need to maintain a strong ref to the sorted, otherwise after it gets GCed, your listener won't be triggered anymore.
I see. thx a lot.
and Please document this behaviour somewhere after confirming it.
This was added to the website docs here: https://realm.io/docs/java/latest/#notifications
We should probably also add a comment to all the places where ChangeListeners can be registered:
Something like:
NOTE: Registering a change listener will not prevent the underlying object from being garbage collected. If the object is garbage collected, the change listener will stop being triggered. To avoid this, keep a strong reference for as long as appropriate e.g. in a class variable.
That is definitely a comment you should add to doc
Closed by #4631
Most helpful comment
oh so that's why my example was working, I used
findAllSorted()instead offindAll().Wow I'm terrible at reproducing bugs if I fix them without noticing 馃槄