realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.45k stars 1.75k forks source link

Not Receiving Object Updates #7863

Open rhysll34 opened 6 months ago

rhysll34 commented 6 months ago

How frequently does the bug occur?

Sometimes

Description

When updating a field in an object I expect that a Flowable taken from the results of a query that returns that object will emit the changed object. In practice nothing is emitted.

When using the debugger, I can see the proxy being updated and the calls to the native functions. I cannot see anything coming back out of the native functions.

Stacktrace & log output

No response

Can you reproduce the bug?

Sometimes - seems intermittent with no discernible pattern

Reproduction Steps

Realm Initialisation:

RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
    .schemaVersion(11)
    .migration(new MyRealmMigration())
    .allowWritesOnUiThread(true)
    .rxFactory(new RealmObservableFactory(false))
    .build();
Realm.setDefaultConfiguration(realmConfiguration);

Query and flowable retrieval where visitId is a primary for the Visit class:

Visit visit = realm.where(Visit.class)
                .equalTo(key visitId).findFirstAsync();

Disposable subscription = visit.asFlowable() // No further emissions after initial result even if data is changed elsewhere
            .filter(realmObject -> realmObject.isLoaded() && realmObject.isValid())
            .firstElement()
            .flatMap(realmObject -> {
                Visit theVisit = (Visit) realmObject;
                listener.setVisit(theVisit);
                UIVisitData uiVisitData = getUiVisitFromVisit(theVisit, listener);
                return Maybe.just(uiVisitData);
            }).observeOn(AndroidSchedulers.mainThread())
            .subscribe(listener);
        listener.setObservableSubscription(subscription);

The proxy for Visit:

public void realmSet$visitStatus(String value) {
    if (proxyState.isUnderConstruction()) {
        if (!proxyState.getAcceptDefaultValue$realm()) {
            return;
        }
        final Row row = proxyState.getRow$realm();
        if (value == null) {
            row.getTable().setNull(columnInfo.visitStatusColKey, row.getObjectKey(), true);
            return;
        }
        row.getTable().setString(columnInfo.visitStatusColKey, row.getObjectKey(), value, true);
        return;
    }

    proxyState.getRealm$realm().checkIfValid();
    if (value == null) {
        proxyState.getRow$realm().setNull(columnInfo.visitStatusColKey);
        return;
    }
    proxyState.getRow$realm().setString(columnInfo.visitStatusColKey, value);
}

Version

10.16.1

What Atlas App Services are you using?

Local Database only

Are you using encryption?

No

Platform OS and version(s)

Android 13, possiblyothers

Build environment

Android Studio version: Hedgehog, but also older version Android Build Tools version: 34 Gradle version: 8.0

rorbech commented 5 months ago

Hi @rhysll34. There is a lot of non-Realm code in play in our report. Could you try to boil you example down to minimal example showing your issue and leave out all non-Realm related (e.g. first element, flatmap, visitors, etc.).

rhysll34 commented 3 months ago

I have added some code to my project to create a minimal example. The code below reflects what I have added. As I interact with the UI the result of Visit::getVisitStatus() should change between various string values, and I would expect to see each new value appear in Logcat. This doesn't always happen. I am fairly certain that the updates are being persisted into Realm. The Visit object is being updated by setting the field value of the object within a Realm transaction. Causing the method below to be caused again will result in the correct value to be retrieved.

class RealmDB {
    RealmObject visit;
    private final Realm realm;

    ...

    public void getVisit(int visitId) {
        visit = realm.where(Visit.class)
            .equalTo("key", visitId).findFirstAsync();

        visit.addChangeListener(a -> {
            Visit visitB = (Visit)a;
            Timber.d("Visit status: %s", visitB.getVisitStatus());
        }); 
    }

    ....
}

The instance of RealmDB should remain in scope while the Activity is still running.