Hi, i am getting crash when removing RealmResults in for loop (Realm 0.89 with fixed iterators):
RealmResults<Foo> results = getResults();
for (Foo foo : results) {
foo.deleteFromRealm();
}
Hi @roughboy
Thank you for reporting.
I couldn't reproduce this.
I wrote the code below in RealmResultsTests.java. collection in the code is RealmResults<AllTypes>.
@Test
public void issue2660() {
assertEquals(TEST_DATA_SIZE, collection.size());
realm.beginTransaction();
for (AllTypes foo : collection) {
foo.deleteFromRealm();
}
realm.commitTransaction();
assertEquals(TEST_DATA_SIZE, collection.size());
realm.refresh();
assertEquals(0, collection.size());
}
Can you provide your stack trace?
Sorry, I totaly forgot this:
04-25 21:06:24.794 14883-15570/com.forzasoftware.forza D/REALM: rowIndex 3 > 2 - invalid!
04-25 21:06:24.795 14883-15570/com.forzasoftware.forza D/REALM: jni: ThrowingException 7, rowIndex > available rows: 3 > 2, .
04-25 21:06:24.795 14883-15570/com.forzasoftware.forza D/REALM: Exception has been throw: rowIndex > available rows: 3 > 2
04-25 21:06:24.811 14883-14883/com.forzasoftware.forza D/AndroidRuntime: Shutting down VM
04-25 21:06:24.814 14883-14883/com.forzasoftware.forza E/ACRA: ACRA caught a NoSuchElementException for com.forzasoftware.forza
java.util.NoSuchElementException: Cannot access index 3 when size is 2. Remember to check hasNext() before using next().
at io.realm.RealmList$RealmItr.next(RealmList.java:824)
at io.realm.RealmList$RealmItr.next(RealmList.java:781)
at io.realm.RealmUtils.deleteSectionImpl(RealmUtils.java:431)
at io.realm.RealmUtils.access$100(RealmUtils.java:27)
at io.realm.RealmUtils$3.execute(RealmUtils.java:388)
at io.realm.Realm$2.run(Realm.java:1316)
at io.realm.internal.async.BgPriorityRunnable.run(BgPriorityRunnable.java:34)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
I narrowed down the scenario. Please use following RealmObject:
public class RealmItem extends RealmObject {
@PrimaryKey
private long id;
private String name;
private RealmItem item;
private RealmList<RealmItem> childList;
}
The key is RealmList
realm.beginTransaction();
for (RealmItem foo : item.getChildList()) {
foo.deleteFromRealm();
}
realm.commitTransaction();
In case of problems, please use the example app (click FAB twice, and then click DELETE ALL to reproduce the issue):
https://drive.google.com/file/d/0B_iAqFNIegRpV19mRjM1VWMxa1E/view?usp=sharing
@roughboy thank you for providing detail information. I'll take a look.
I found this comment
final void checkConcurrentModification() {
// A Realm ListView is backed by the original Table and not a TableView, this means
// that all changes are reflected immediately. It is therefore not possible to use
// the same version pinning trick we use for RealmResults (avoiding calling sync_if_needed)
I think that comment explains the problem, I guess even though RealmResults<T> modifications are sent only on looper events, the RealmList still updates immediately when removing elements during a transaction? Is that possible?
I'm not a Realm person, haha.
@Zhuinden thanks!
@roughboy As @Zhuinden said, removing RealmList in for-each does not work.
Please use RealmList.deleteAllFromRealm() which has been added in 0.89.0 instead.
It is the same as removing items for normal ArrayLists. That is not allowed either and will throw ConcurrentModificationException.
In our case, actually detecting that concurrent modification is unfortunately rather hard, so it hasn't been implemented. Guess we should re-evaluate that decision.
I added an issue for tracking that /cc @zaki50 #2688
Hi @roughboy I assume the above answered you question. We will work on improving the situation in #2688
@cmelchior how would you delete the objects if not all were being deleted? i'm testing a condition for each object and testing true would mean it is deleted. i'm using a for each loop and am getting the same error.
final Integer itemClassId = 5;
final List<Item> items = UserSingleton.getUser().getItems(); //UserSingleton.getUser().getItems() is of type RealmList
if (items.size() != 0){
RealmSingleton.getUserInstance().executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
for (Item item : items){
if (item.getClassId().equals(itemClassId)){
item.deleteFromRealm();
}
}
}
});
}
java.util.NoSuchElementException: Cannot access index 1 when size is 0. Remember to check hasNext() before using next().
at io.realm.RealmList$RealmItr.next(RealmList.java:818)
at io.realm.RealmList$RealmItr.next(RealmList.java:775)
Reverse iterate your RealmList by index
for(int i = list.size()-1; i >= 0; i--) {
Item item = list.get(i);
...
@Zhuinden Thank you.
Can selective deletion of RealmList items be done in Kotlin, though? Kotlin doesn't have a "by index" for loop.
Since this comment was made we have a RealmResults.snapshot() method that keeps the collection stable even when objects are deleted.
Not to mention you could just do for(index in realmResults.size-1 downTo 0)
Most helpful comment
Reverse iterate your RealmList by index