Zeyad Gasser
Zeyad Gasser

Reputation: 1546

java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created

I am trying to combine retrofit 2 with RxJava and Realm, by saving the response of the service call returned by the retrofit observable to local database using realm. So i get an exception saying that Realm access from incorrect thread. Here is my code :

Trail 1:

restApi.userEntityList()
            .map(userEntityDataMapper::transformAllToRealm)
            .doOnNext(userRealmModels -> {
                if (userRealmModels != null){
                    mRealm = Realm.getInstance(mContext);
                    mRealm.asObservable()
                            .map(realm -> mRealm.copyToRealmOrUpdate(userEntity))
                            .subscribe(new Subscriber<Object>() {
                                @Override
                                public void onCompleted() {

                                }

                                @Override
                                public void onError(Throwable e) {
                                    e.printStackTrace();
                                }

                                @Override
                                public void onNext(Object o) {
                                    Log.d("RealmManager", "user added!");
                                }
                            });
                }})
            .map(userEntityDataMapper::transformAll)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<List<User>>() {
                 @Override
                 public void onCompleted() {
                     hideViewLoading();
                 }

                 @Override
                 public void onError(Throwable e) {
                     hideViewLoading();
                     showErrorMessage(new DefaultErrorBundle((Exception) e));
                     showViewRetry();
                 }

                 @Override
                 public void onNext(List<User> users) {
                     showUsersCollectionInView(users);
                 }
            });

Trail 2:

restApi.userEntityList()
            .map(userEntityDataMapper::transformAllToRealm)
            .doOnNext(userRealmModels -> {
                if (userRealmModels != null) {
                    mRealm = Realm.getInstance(mContext);
                    mRealm.beginTransaction();
                    mRealm.copyToRealmOrUpdate(userEntity);
                    mRealm.commitTransaction();
                }
            })
            .map(userEntityDataMapper::transformAll)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<List<User>>() {
                 @Override
                 public void onCompleted() {
                     hideViewLoading();
                 }

                 @Override
                 public void onError(Throwable e) {
                     hideViewLoading();
                     showErrorMessage(new DefaultErrorBundle((Exception) e));
                     showViewRetry();
                 }

                 @Override
                 public void onNext(List<User> users) {
                     showUsersCollectionInView(users);
                 }
            });

Logcat :

W/System.err: 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:349)
at io.realm.BaseRealm.commitTransaction(BaseRealm.java:291)
at io.realm.Realm.commitTransaction(Realm.java:108)
at com.zeyad.cleanarchitecturet.data.db.RealmManagerImpl.put(RealmManagerImpl.java:66)
at com.zeyad.cleanarchitecturet.data.db.RealmManagerImpl.putAll(RealmManagerImpl.java:91)
at com.zeyad.cleanarchitecturet.data.repository.datasource.CloudUserDataStore$2.call(CloudUserDataStore.java:36)
at com.zeyad.cleanarchitecturet.data.repository.datasource.CloudUserDataStore$2.call(CloudUserDataStore.java:32)
at rx.Observable$11.onNext(Observable.java:4445)
at rx.internal.operators.OperatorDoOnEach$1.onNext(OperatorDoOnEach.java:80)
at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:54)
at rx.internal.operators.OperatorMerge$MergeSubscriber.emitScalar(OperatorMerge.java:477)
at rx.internal.operators.OperatorMerge$MergeSubscriber.tryEmit(OperatorMerge.java:435)
at rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:228)
at rx.internal.operators.OperatorMerge$MergeSubscriber.onNext(OperatorMerge.java:142)
at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:54)
at retrofit.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:113)
at retrofit.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:88)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable$2.call(Observable.java:162)
at rx.Observable$2.call(Observable.java:154)
at rx.Observable.unsafeSubscribe(Observable.java:8098)
at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at rx.schedulers.ExecutorScheduler$ExecutorSchedulerWorker.run(ExecutorScheduler.java:98)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: java.util.ArrayList.class
at rx.exceptions.Exceptions.throwOrReport(Exceptions.java:187)
at rx.internal.operators.OperatorDoOnEach$1.onNext(OperatorDoOnEach.java:82)
... 29 more

Upvotes: 0

Views: 2146

Answers (2)

Zeyad Gasser
Zeyad Gasser

Reputation: 1546

I found out how to fix it. To save server responses from retrofit 2 to realm then pass to the UI. I made retrofit return realm objects by overriding the Gson Builder like this:

Retrofit.Builder()
            .baseUrl(RestApi.API_BASE_URL)
            .client(okHttpClient)
            .callbackExecutor(new JobExecutor())
            .addConverterFactory(GsonConverterFactory.create(new GsonBuilder()
                    .setExclusionStrategies(new ExclusionStrategy() {
                        @Override
                        public boolean shouldSkipField(FieldAttributes f) {
                            return f.getDeclaringClass().equals(RealmObject.class);
                        }

                        @Override
                        public boolean shouldSkipClass(Class<?> clazz) {
                            return false;
                        }
                    }).create()))
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();

Then:

restApi.userRealmList()
    .doOnNext(userRealmModels -> {
        if (userRealmModels != null) {
            Realm realm = Realm.getInstance(mContext);
            realm.beginTransaction();
            realm.copyToRealmOrUpdate(userRealmModels);
            realm.commitTransaction();
            realm.close();
        }})
    .map(userEntityDataMapper::transformAll)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Subscriber<List<User>>() {
         @Override
         public void onCompleted() {
             hideViewLoading();
         }

         @Override
         public void onError(Throwable e) {
             hideViewLoading();
             showErrorMessage(new DefaultErrorBundle((Exception) e));
             showViewRetry();
         }

         @Override
         public void onNext(List<User> users) {
             showUsersCollectionInView(users);
         }
    });

Upvotes: 1

Brijesh Chopda
Brijesh Chopda

Reputation: 195

I think you have created Realm objects using main thread and you are using it in another or background thread. If so, then use them in the same thread they are created. i.e.

((Activity)mContext).runOnUiThread(new Runnable() {
   @Override
   public void run() {
        // You code of realm goes here
        if (userRealmModels != null) {
               mRealm = Realm.getInstance(mContext);
               mRealm.beginTransaction();
               mRealm.copyToRealmOrUpdate(userEntity);
               mRealm.commitTransaction();
         }
   }
});

Upvotes: 0

Related Questions