I just open an month old Android Studio project and updated my Realm Library to 3.0.1 (3.0.0 works fine)
which contains a RecyclerView with checkboxes, when I click on them I get following error which never happend before this update 3.0.1;
java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
at muddzdev.shoppist.model.ListItem.setCheckedItem(ListItem.java:73)
at muddzdev.shoppist.itemlist.ItemListAdapter$ViewHolder.onCheckedChanged(ItemListAdapter.java:140)
at android.widget.CompoundButton.setChecked(CompoundButton.java:166)
at muddzdev.shoppist.itemlist.ItemListAdapter.onBindViewHolder(ItemListAdapter.java:89)
at muddzdev.shoppist.itemlist.ItemListAdapter.onBindViewHolder(ItemListAdapter.java:30)
ListItem.java:73:
public void setCheckedItem(Realm realm, final int checkFlag) {
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
if (checkFlag == 1) {
setIsBought(1);
} else if (checkFlag == 0) {
setIsBought(0);
}
}
});
}
ItemListAdapter.java:89
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
String price = "";
final ListItem listItem = getData().get(position);
if (listItem.getPrice() != 0) {
if (valutaPos.equals(RIGHT)) {
price = Utils.getFormattedPrice(listItem.getPrice()) + valutaSign;
} else {
price = valutaSign + Utils.getFormattedPrice(listItem.getPrice());
}
}
holder.title1.setText(listItem.getTitle());
holder.title2.setText(listItem.getLocation() + " " + price);
holder.checkBox.setChecked(listItem.getIsBought() == 1);
}
In my View holder: ItemListAdapter.java:30
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ListItem listItem = getData().get(getAdapterPosition());
if (isChecked) {
listItem.setCheckedItem(realm, 1);
title1.setTextColor(Color.LTGRAY);
title2.setTextColor(Color.LTGRAY);
} else if (!isChecked) {
listItem.setCheckedItem(realm, 0);
title1.setTextColor(defaultColorTx1);
title2.setTextColor(defaultColorTx2);
}
}
Hi,
We didn't release 3.0.1 (we released 3.1.0 after 3.0.0).
What version are you actually using?
@zaki50 I use right now classpath "io.realm:realm-gradle-plugin:2.3.2", but everything after that i.e 3.0.0 + is crashing for me
You should update to Realm 3.1.2 if you want to switch to a higher version.
If it still fails, then you should use executeTransactionAsync instead of executeTransaction.
@Zhuinden I just wanted to know why its happening. What changes was made in Realm? Is it to force us to use async when writing to the database when dealing with RecyclerView?
@Muddz If your app works fine with 3.0.0 and does not work 3.1.0 or later, I would think that #4245 is the cause.
https://github.com/realm/realm-java/blob/master/CHANGELOG.md#enhancements-1
Listeners on RealmList and RealmResults will be triggered immediately when the transaction is committed on the same thread (#4245).
did you have setStableIds(true) in your adapter?
@beeender yes i do
@Muddz you might want to disable that.
@Muddz The RealmRecycleView.getItemId() doesn't return stable id:
- Returns the current ID for an item. Note that item IDs are not stable so you cannot rely on the item ID being the
- same after notifyDataSetChanged() or {#link #updateData(OrderedRealmCollection)} has been called.
If you want it to return a stable ID you have to overwrite it and make it return a stable id.
@Zhuinden @beeender I have found the problem:
setting autoUpdate in the super constructor to true as: super(null, true);
will cause my app tojava.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
Removing or setting the hasStableId() has no effect
Realm Adapter version: 'io.realm:android-adapters:2.0.0'
RealmDatabse version: "io.realm:realm-gradle-plugin:3.1.3"
@Muddz No, you haven't found the problem, if you disable auto-updates then your Adapter will get out of sync when you insert or remove elements from the Realm that are shown in the RecyclerView.
However, I've read the stack trace again, and now I understand what's wrong.
Read problem and fix below.
When you call
holder.checkBox.setChecked(listItem.getIsBought() == 1);
Then this triggers your @OnCheckedChanged(R.id.checkbox) check change listener on your view holder
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
ListItem listItem = getData().get(getAdapterPosition());
if (isChecked) {
listItem.setCheckedItem(realm, 1);
which begins a write transaction that will change your RealmObject
public void setCheckedItem(Realm realm, final int checkFlag) {
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
if (checkFlag == 1) {
setIsBought(1);
However, when executeTransaction() calls commitTransaction(), it will call all RealmChangeListeners immediately and synchronously.
But this will cause a "change" and therefore notifyItemRangeChanged() is called for the changed element, during onBindViewHolder() calls, aka while adapter is calculating itself.
SOLUTION: You should only call realm.executeTransaction() if checkFlag == 1 but RealmObject's isBought != 1 (or if checkFlag == 0 and isBought != 0)
Currently you are writing 1 to the realmobject even if the value in it is 1, and you execute a transaction for each element; so this fix is also a performance improvement for your app.
@Zhuinden Thanks it worked!!
Now I check my value as following:
public void setCheckedItem(Realm realm, final int checkFlag) {
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
if (checkFlag == 1 && isBought !=1) {
setIsBought(1);
}
}
});
}
So basically my mistake was that I was writing a value that already exsisted in my realm records?
setting setIsBought(1); to 1 even when isBought was already 1?
Yup, although you still start a transaction even though you don't need to actually do a write.
Alas, it'll work :smile:
The problem was caused by that just initializing the check box triggered a write which "modified" the object to its currently existing value, which was handled as a "change" by Realm for that given property
@Zhuinden Allrighty! I will remember that for all my writing operations!
I really appreciate your commitment for helping people here! You deserve a medal!
Glad I could help :smile:
@Zhuinden Almost a year later, your answer was useful.
@Zhuinden this error was killing me for 2 days... events were firing twice and it was very subtle...
thanks a lot!
Most helpful comment
@Zhuinden Almost a year later, your answer was useful.