4

I'm getting this exception reading/writing from Realm

06-19 09:49:26.352 11404-11404/****** E/ContentValues: loadData: OnError Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created. java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created. at io.realm.BaseRealm.checkIfValid(BaseRealm.java:385) at io.realm.RealmResults.isLoaded(RealmResults.java:115) at io.realm.OrderedRealmCollectionImpl.size(OrderedRealmCollectionImpl.java:307) at io.realm.RealmResults.size(RealmResults.java:60) at java.util.AbstractCollection.isEmpty(AbstractCollection.java:86) at /****** .lambda$loadData$0(SplashPresenter.java:42) at /****** $$Lambda$1.test(Unknown Source) at io.reactivex.internal.operators.observable.ObservableFilter$FilterObserver.onNext(ObservableFilter.java:45) at io.reactivex.observers.SerializedObserver.onNext(SerializedObserver.java:111) at io.reactivex.internal.operators.observable.ObservableDelay$DelayObserver$1.run(ObservableDelay.java:84) at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:59) at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:51) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:761)

This is the code:

  mSubscribe = Observable.just(readData())
            .delay(DELAY, TimeUnit.SECONDS)
            .filter(value -> !value.isEmpty())
            .switchIfEmpty(createRequest())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread()).subscribe(data -> {
                getView().hideLoading();
                writeData(data);
            }, 
           (throwable -> {
            }));

Read data

  private List<CategoryModel> readData() {
    Realm defaultInstance = Realm.getDefaultInstance();
    List<CategoryModel> title = defaultInstance.where(CategoryModel.class).findAllSorted("title");

    defaultInstance.close();
    return title;
}

Write data

private void writeData(List<CategoryModel> categoryModels) {

        try {
            Realm defaultInstance = Realm.getDefaultInstance();
            defaultInstance.executeTransactionAsync(realm -> realm.insertOrUpdate(categoryModels));
            defaultInstance.close();
        } finally {
            getView().notifyActivity(categoryModels);
        }
    }

How can I follow this logic using the proper threads?

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
rafaelasguerra
  • 2,685
  • 5
  • 24
  • 56

4 Answers4

6

The only rule to using Realm across threads is to remember that Realm, RealmObject or RealmResults instances cannot be passed across threads.

When you want to access the same data from a different thread, you should simply obtain a new Realm instance (i.e. Realm.getDefaultInstance()) and get your objects through a query (then close Realm at the end of the thread).

The objects will map to the same data on disk, and will be readable & writeable from any thread! You can also run your code on a background thread using realm.executeTransactionAsync() like this .

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
Ajeet Choudhary
  • 1,969
  • 1
  • 17
  • 41
2

How can i follow this logic using the proper threads?

By not trying to read on Schedulers.io() for your UI thread (Realm gives auto-updating lazy-loaded proxy views that provide change notifications for your data on the UI thread, after all).


So instead of this

 mSubscribe = Observable.just(readData())
        .delay(DELAY, TimeUnit.SECONDS)
        .filter(value -> !value.isEmpty())
        .switchIfEmpty(createRequest())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread()).subscribe(data -> {
            getView().hideLoading();
            writeData(data);
        }, 
       (throwable -> {
        }));

private List<CategoryModel> readData() {
    Realm defaultInstance = Realm.getDefaultInstance();
    List<CategoryModel> title = defaultInstance.where(CategoryModel.class).findAllSorted("title");

    defaultInstance.close();
    return title;
}

private void writeData(List<CategoryModel> categoryModels) {
    try {
        Realm defaultInstance = Realm.getDefaultInstance();
        defaultInstance.executeTransactionAsync(realm -> realm.insertOrUpdate(categoryModels));
        defaultInstance.close();
    } finally {
        getView().notifyActivity(categoryModels);
    }
}

You're supposed to have something like

private Observable<List<CategoryModel>> readData() { // Flowable with LATEST might be better.
    return io.reactivex.Observable.create(new ObservableOnSubscribe<List<CategoryModel>>() {
        @Override
        public void subscribe(ObservableEmitter<List<CategoryModel>> emitter)
                throws Exception {
            final Realm observableRealm = Realm.getDefaultInstance();
            final RealmResults<CategoryModel> results = observableRealm.where(CategoryModel.class).findAllSortedAsync("title");
            final RealmChangeListener<RealmResults<CategoryModel>> listener = results -> {
                if(!emitter.isDisposed() && results.isLoaded()) {
                    emitter.onNext(results);
                }
            };

            emitter.setDisposable(Disposables.fromRunnable(() -> {
                if(results.isValid()) {
                    results.removeChangeListener(listener);
                }
                observableRealm.close();
            }));
            results.addChangeListener(listener);
        }
    }).subscribeOn(AndroidSchedulers.mainThread())
            .unsubscribeOn(AndroidSchedulers.mainThread());
}

private void setSubscription() {
    mSubscribe = readData()
            .doOnNext((list) -> {
                if(list.isEmpty()) {
                    Single.fromCallable(() -> this::createRequest)
                            .subscribeOn(Schedulers.io())
                            .subscribe((data) -> {
                                writeData(data);
                            });
                }
            }).subscribe(data -> {
                if(!data.isEmpty()) {
                    getView().hideLoading();
                    getView().notifyActivity(data);
                }
            }, throwable -> {
                throwable.printStackTrace();
            });
}

private void writeData(List<CategoryModel> categoryModels) {
    try(Realm r = Realm.getDefaultInstance()) {
        r.executeTransaction(realm -> realm.insertOrUpdate(categoryModels));
    }
}

void unsubscribe() {
    mSubscribe.dispose();
    mSubscribe = null;
}

This way (if I didn't mess anything up), you end up with the reactive data layer described here and here, except without the additional overhead of mapping out the entire results.

EDIT:

Since Realm 4.0, it is possible to expose a RealmResults directly as a Flowable (on the UI thread, or background looper thread).

public Flowable<List<MyObject>> getLiveResults() {
    try(Realm realm = Realm.getDefaultInstance()) {
        return realm.where(MyObject.class) 
                    .findAllAsync()
                    .asFlowable()
                    .filter(RealmResults::isLoaded);
    }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • @Radu depends on what you're having trouble with – EpicPandaForce Nov 16 '17 at 15:13
  • I'm having trouble with the absolute garbage code this produces. Realm does not work well with threading, and therefor with Rxjava. Objects deserialized autoamtically with GSON are not passable to the UI Thread because they're Realm objects...absolue garbage. I have to create 2 pojos, one for Realm and one for the UI Thread and network requests.... – Radu Nov 16 '17 at 15:24
  • Objects deserialized by GSON should be saved into the Realm on background thread, and the RealmResults + RealmChangeListener (or RealmResults exposed as Flowable, whatever) will receive the new state. There is no need to directly send GSON response to UI thread. There is also no need to manually handle passing between Threads because RealmResults already manages that internally, so you just need to subscribe to it. Have you read the docs? – EpicPandaForce Nov 16 '17 at 15:29
  • I have no idea what you're talking about. I use GSON internally inside RxJava 2 to deserialize my POJOs....and then I receive them on the UI Thread and expect to use them. This breaks because Realm created objects on the background thread are not accesible on the UI Thread. According to the docs.... https://stackoverflow.com/questions/34230451/android-realm-retrofit-2-gson https://gist.github.com/cmelchior/ddac8efd018123a1e53a – Radu Nov 17 '17 at 10:38
1

You need to extract required data from realm objects into POJO and emit POJOs using map operator, so that view objects can updated with data from realm using pojo on android main thread.

Arnav Rao
  • 6,692
  • 2
  • 34
  • 31
-1

You can only manipulate Realm objects in a transaction or only in the thread you read/write these objects. In your case you are getting a RealmResult from the readData method and using RxJava you are switching threads which caused the exception. Use copyFromRealm to get the data from realm which will return them as plain objects rather than realm objects.