Realm DB查询结果应为非托管对象却呈托管状态的问题
你遇到的这个困惑其实很常见——核心在于变量的声明类型≠对象的实际类型。让我拆解一下:
虽然你把stories声明成了List<story>,但realm.where(story.class).findAllAsync()返回的实际对象是RealmResults<story>。而RealmResults本身是实现了Java的List接口的,这就导致你可以把它赋值给List类型的变量,但它本质上还是Realm的实时托管查询结果集,里面的每一个story对象自然也还是托管状态——这就是为什么你直接修改属性时会触发Cannot modify managed objects outside of a write transaction错误。
关键知识点回顾
RealmResults是Realm的实时同步集合,它会和数据库自动保持一致,里面的所有对象都是托管的,必须在Realm写事务中才能修改。- 向上转型(把
RealmResults赋值给List变量)不会改变对象本身的类型,只是隐藏了它的Realm特有方法和特性。
针对你的场景的两种解决办法
1. 如果你只需要读取数据,不需要修改这些对象
把托管的RealmResults复制成普通的Java对象集合,这样就可以自由修改属性了:
stories.addChangeListener(new RealmChangeListener<RealmResults<story>>() { @Override public void onChange(RealmResults<story> storydata) { if (storydata.size() != 0) { // 将托管结果复制为非托管的List,里面的对象都是普通Java对象 List<story> unmanagedStories = realm.copyFromRealm(storydata); madapter = new StoriesAdapter(unmanagedStories, getBaseContext(), MR); mrecyclerview.setNestedScrollingEnabled(false); mrecyclerview.setLayoutManager(new LinearLayoutManager(getBaseContext())); mrecyclerview.setAdapter(madapter); } } });
这样修改madapter.storyList.get(3).setTitle("Wonderland")就不会报错了,因为这些对象已经和Realm数据库解绑了。
2. 如果你需要修改对象并同步到数据库
这种情况下必须在Realm的写事务中操作,比如:
// 假设在需要修改的触发点(比如按钮点击)执行以下代码 realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm realm) { // 获取要修改的托管对象(注意要确保对象处于有效状态) story targetStory = realm.where(story.class).findAll().get(3); targetStory.setTitle("Wonderland"); } }, new Realm.Transaction.OnSuccess() { @Override public void onSuccess() { // 修改成功后通知适配器更新UI madapter.notifyItemChanged(3); } }, new Realm.Transaction.OnError() { @Override public void onError(Throwable error) { // 处理修改失败的情况 Log.e("Realm", "修改失败", error); } });
这种方式下,修改会自动同步到Realm数据库,而且因为RealmResults是实时的,你的RecyclerView也会收到数据更新的通知(如果适配器正确处理的话)。
另外提个小建议:如果你的适配器需要利用Realm的实时更新特性,其实可以直接让适配器接收RealmResults<story>作为数据源,然后给它添加ChangeListener,这样数据变化时可以自动更新UI,不用手动复制~
内容的提问来源于stack exchange,提问作者Sudhanshu Gaur

