Receive change updates on a RealmQuery
When the RealmQuery changes, the RealmChangeListener is fired
The RealmChangeListener is never fired
I create a query (realm.where(Test.class).findAll()), and add a RealmChangeListener to the returned RealmResults (I maintain a strong reference to the RealmResults).
If the query was empty when I made it, the RealmChangeListener never fires, no matter how many times I insert an object that matches the query after that. If there is an object when the query is made, the the RealmChangeListener does fire, even if all the objects are removed and then another one is inserted.
Realm version(s): 3.3.1
Realm sync feature enabled: no
Android Studio version: 3.0 Canary 3
Which Android version and device: Pixel 7.1.2
My temporary fix is right before I make the query, I insert a dummy object. Obviously not a great solution...
Hello!
I made a test below but it could not reproduce your issue:
@Test
@RunTestInLooperThread
public void addChangeListener_initWithEmptyResults() {
Realm realm = looperThread.getRealm();
RealmResults<AllTypes> results = realm.where(AllTypes.class).findAll();
assertTrue(results.isEmpty());
results.addChangeListener(new RealmChangeListener<RealmResults<AllTypes>>() {
@Override
public void onChange(RealmResults<AllTypes> allTypes) {
assertEquals(1, allTypes.size());
looperThread.testComplete();
}
});
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.createObject(AllTypes.class);
}
});
}
Is it possible for you to make a simple project to show the issue?
@beeender I haven't been able to repro it in another project, but I've tracked it down to what I think the issue is (although I don't know why it would cause an issue).
Here's my sequence of events (I'm using rx 2.0):
Flowable.defer, subscribing on a HandlerThreadRealm.where(Test.class).findAll() on the same threadRealmResults to a FlowableOnSubscriberFlowableOnSubscriber.subscribe I emit the RealmResults and then add a RealmChangeListener to it, which emits the RealmResults when it firesFlowable on RealmResults.isLoadedRealmResults.createSnapshotOrderedRealmCollectionSnapshot.size != 0OrderedRealmCollectionSnapshot that was emitted in a 30 second windowOrderedRealmCollectionSnapshot.deleteAllFromRealm in a transactionIf I follow these steps, then the RealmChangeListener never fires. If I follow steps 1-6 and don't do step 7 it does fire ¯_(ツ)_/¯
UPDATE: Some things that I forgot to mention are that the issue occurs only if the RealmResults from step 3 is empty initially, and that after step 7, a Test object is inserted, but the RealmChangeListener still doesn't fire after that.
I filter by OrderedRealmCollectionSnapshot.size != 0
But why? The snapshot is never updated, so of course you'll never receive an emission...
@Zhuinden just to clarify, I'm calling filter on a Flowable<OrderedRealmCollectionSnapshot>, which will only emit if the predicate is true (in this case if OrderedRealmCollectionSnapshot.size > 0).
The upstream should still emit, because it is tied to the RealmChangeListener.
I did forget to mention a few things in my post above; I will update it now.
I still don't understand why createSnapshot() is involved. You might want to include more code.
FWIW the issue still occurs if I keep it as a RealmResults.
I added some steps above to delineate more of what I'm doing.
I use createSnapshot because I want a stable results set.
The objects will still become invalidated inside it if they're deleted, ya know.
Deletion is fine. I just want to make sure other objects aren't added.
I changed my process to copy from the RealmResults, and do an in query later to delete them. Less performant, but it works.
I'll have to revisit later, but I believe that the filter on non empty (this time on the unmanaged list) still caused the issue where the RealmChangeListener wouldn't fire. At this point not sure if it's a realm issue, an rx issue, or both. I'll update when I make a sample project.
in step 4, do you keep a strong reference to the listener added to RealmResults?
Sort of; it's captured in a lambda.
@eygraber are you able to reproduce it with a sample project? thanks!
@beeender I haven't been able to set aside time for it yet.
@eygraber Any progress?
@kneth sorry I haven't got a chance to try. Crazy sprints at work.
@eygraber Any news to share?
I still think it's caused by the snapshot() calls.
Hey @eygraber,
Haven't heard from you in a while. I'm going to close this issue. By all means, if this problem recurs or anything else comes up, feel free to open a new one.
Cheers,
-blake
Hello people. I have the same issue.
No, you probably have a different but similar problem.
This is part of my code.
This is presenter
class FavoritePresenter(private val view: FavoriteContract.FavoriteView,
private val baseContext: Context?) :
FavoriteContract.FavoritePresenter,
RealmChangeListener
private val realm = Realm.getDefaultInstance()
init {
view.setPresenter(this)
}
override fun start() {
realm.where(ClippingModel::class.java).findAll().addChangeListener(this)
}
override fun onChange(t: RealmResults<ClippingModel>) {
if (!realm.isInTransaction) {
realm.beginTransaction()
val clippings = realm.copyFromRealm(t)
realm.commitTransaction()
view.onShowClippingCollection(clippings)
}
}
}
This is fragment
class FavoriteFragment : BaseFragment(), FavoriteContract.FavoriteView {
private lateinit var presenter: FavoriteContract.FavoritePresenter
private var clippingAdapter: ClippingRVAdapter? = null
companion object {
fun newInstance(): FavoriteFragment = FavoriteFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
FavoritePresenter(this, context)
return inflater.inflate(R.layout.fragment_favorite, container, false)
}
override fun onStart() {
super.onStart()
presenter.start()
}
}
_I am never getting callback in onChange() if RealmResults is empty._
See https://realm.io/docs/java/latest/api/io/realm/RealmResults.html
Adds a change listener to this RealmResults.
Registering a change listener will not prevent the underlying RealmResults from being garbage collected. If the RealmResults 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.public class MyActivity extends Activity { private RealmResults<Person> results; // Strong reference to keep listeners alive @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); results = realm.where(Person.class).findAllAsync(); results.addChangeListener(new RealmChangeListener<RealmResults<Person>>() { @Override public void onChange(RealmResults<Person> persons) { // React to change } }); } }
On top of that, you're calling copyFromRealm instead of, like, copyToRealmOrUpdate or insertOrUpdate or anything that actually writes into the db. Also, running synchronous transaction inside a change listener?
Are you trying to get an infinite loop? 😕
What are you doing???
Ok, it works perfectly. Thanks for help @Zhuinden
I still don't know why you have a transaction in your change listener though, you don't need a transaction to read things
Yep, i forgot to refactor it ))