realm / realm-java

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

auto-refresh on Main Thread not happening #3541

Closed miquelbeltran closed 8 years ago

miquelbeltran commented 8 years ago

Goal

On app start, no stored data, completely empty Realm I obtain a Realm instance for the Main Thread (in the Main Activity), let's call it realmUi.

Then I use RxJava for:

The data is available in the realmUi as soon as I finish the transaction in the background thread.

Actual Results

Sometimes, the data in the realmUi is not yet available. As it seems that the auto refresh functionality of the realmUi instance has not run yet.

I've been learning a lot about how the auto-refresh works, reading here: https://realm.io/docs/java/latest/#auto-refresh

And also some other reported Issues:

https://github.com/realm/realm-java/issues/3476 https://github.com/realm/realm-java/issues/3422 https://github.com/realm/realm-java/issues/3427

.refresh() method was removed back in Realm ~0.90

I kind of found a way to force a refresh, which is running an empty transaction on my realmUi. However I would like to have a better solution.

.doOnNext(Void -> realmUi.executeTransaction(realm -> {}))

For example, I would like to wait on my background thread until all the other realm instances have been refreshed.

There's also a nice proposal to have an async transaction as Observable.

https://github.com/realm/realm-java/issues/2126

Also, if I understand correctly, if I run an async transaction from realmUi, the realmUi will be refreshed when I get the onSuccess. So I could wrap the async transaction into an Observable that emits onNext + onCompleted on onSuccess and onError on transaction's error.

Steps & Code to Reproduce

The issue happens very randomly, as it depends on the Main thread not being quick enough to sync the realm database.

Code Sample

My code looks like this:

The apiCall-write-read observable

        // Request API data on IO Scheduler
        Observable<WeatherRealm> observable =
                service.getWeather(name, getString(R.string.api_key))
                        .subscribeOn(Schedulers.io())
                        // Write to Realm on Computation scheduler
                        .observeOn(Schedulers.computation())
                        .map(this::writeToRealm)
                        // Read results in Android Main Thread (UI)
                        .observeOn(AndroidSchedulers.mainThread())
                        // trick to force a refresh in realmUi, commented out
                        // .doOnNext(Void -> realmUi.executeTransaction(realm -> {}))
                        .map(this::readFromRealm);

Write to Realm, happening on the Schedulers.computation and inside a transaction.

    private String writeToRealm(WeatherResponse weatherResponse) {
        Realm realm = Realm.getDefaultInstance();
        realm.executeTransaction(transactionRealm -> {
            WeatherRealm weatherRealm = findInRealm(transactionRealm, weatherResponse.getName());
            if (weatherRealm == null)
                weatherRealm = transactionRealm.createObject(WeatherRealm.class, weatherResponse.getName());
            weatherRealm.setTemp(weatherResponse.getMain().getTemp());
        });
        realm.close();
        return weatherResponse.getName();
    }

Read operation, sometimes returns null because the realmUi is not refreshed

    private WeatherRealm readFromRealm(String name) {
        return realmUi.where(WeatherRealm.class).equalTo("name", name).findFirst();
    }

Version of Realm and tooling

Realm version(s): 1.2.0

Android Studio version: 2.2

Which Android version and device: 6.0.1 OneplusX

Zhuinden commented 8 years ago

The referred issue https://github.com/realm/realm-java/issues/3427 only occurs with Realm 2.0.0.

In Realm 1.2.0, commitTransaction() sends a REALM_CHANGED event to the handler, which should run before Rx's own message... but does it really?

This method is less messy than committing an empty transaction: http://stackoverflow.com/a/38839808/2413303

Local commits force async queries to become sync, so they're not that great.

Technically the more reliable way of doing things would be this

    Observable<Void> networkObservable =
            service.getWeather(name, getString(R.string.api_key))
                    .subscribeOn(Schedulers.io())
                    // Write to Realm on Computation scheduler
                    .observeOn(Schedulers.computation())
                    .map(this::writeToRealm);

    Observable<WeatherRealm> realmObservable =
            realm.where(WeatherRealm.class).findAllAsync()
                        .asObservable()
                        .filter(RealmResults::isLoaded);
miquelbeltran commented 8 years ago

Thanks a lot @Zhuinden your solution works like a charm.

I close the Issue, but if anyone else has a proposal please feel free to contribute.