1

I already gone through these similar questions for the issue, but could not find the answer

SO question1 , SO question2 and SO question3

My application flow is on button click, network is requested as follows using Volley. Included only relevant code . Error getting in ActivityCustomer in the following line

Object obj = realmObj.where(ExplorerFolderData.class)
                    .equalTo(Constants.DBPARENTID,"0")
                    .or()
                    .equalTo(Constants.DBPARENTID,"-1")
                    .findAllAsync();

Error:

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

1) MyApplication

public class MyApplication extends Application
{
    private static Context appContext;
    @Override
    public void onCreate() {
        super.onCreate();
        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this)
                .name(Realm.DEFAULT_REALM_NAME)
                .schemaVersion(0)
                .deleteRealmIfMigrationNeeded()
                .build();
        Realm.setDefaultConfiguration(realmConfiguration);
        appContext = getApplicationContext();
    }

     public static Context getAppContext(){
        return appContext;
    }
}

2) Interface OnAsyncTaskComplition

//This interface will get back the status to Activity/Fragment
public interface OnAsyncTaskComplition {
    public void networkResponse(ResultObject responseObject);

}

3) NetworkProcessor

public class NetworkProcessor{

    private OnAsyncTaskComplition mCaller;

    public NetworkProcessor(Activity activity){
        //setting caller Activity/Fragment to get back data
        mCaller=(OnAsyncTaskComplition)activity;
        processNetworkData();
    }

    //Method for Volley Network Procesing
    public void processNetworkData(){

    JsonObjectRequest jsonObjReq = new JsonObjectRequest(methodReq,urlBuffer.toString(),null,
                        new Response.Listener<JSONObject>(){

        @Override
        public void onResponse(JSONObject response) {
                JsonProcessor jsonProcessor=new JsonProcessor();

                    mCaller.networkResponse(jsonProcessor.getThingList(response));

                }
            }, new Response.ErrorListener(){
            @Override
            public void onErrorResponse(VolleyError error) {
               //Handle error also back to caller
             }
            });
    }

    }

4) JsonProcessor

public class JsonProcessor {

    public status void getThingList(JSONObject response){
        boolean status=false;
        try{
            RealmProcessor realmProcessor=RealmProcessor.with(MyApplication.getAppContext());
            Realm realmObj =  realmProcessor.getRealm();

            //Code for setting values to RealmObject class  ExplorerFolderData

            realmObj.beginTransaction();
            realmObj.copyToRealm(ExplorerFolderData RealmObject which has values populated);
            realmObj.commitTransaction();
            status=true;
        }catch(Exception e){

        }
    }
}

5) RealmProcessor

public class RealmProcessor {

    private static RealmProcessor instance;
    private Realm realm;

     private RealmProcessor(Context context) {
        realm = Realm.getDefaultInstance();
    }

    public static RealmProcessor with(Context context) {

        if (instance == null) {
            instance = new RealmProcessor(context);
        }
        return instance;
    }

    public Realm getRealm() {
        return realm;
    }

    }

6) Activity class ActivityCustomer

public class ActivityCustomer extends AppBaseActivity implements OnAsyncTaskComplition
{

    //Method called on Button click
    private void callNetwork(){
    new NetworkProcessor(this);
    }

    @Override
    public void networkResponse(ResultObject responseObject) {

        new ExplorerDBOperation().execute();

    }

    class ExplorerDBOperation extends AsyncTask<Void,Boolean,Boolean> {
        ProgressDialog dialog;

        @Override
        protected Boolean doInBackground(Void... params) {
            RealmProcessor realmProcessor=RealmProcessor.with(MyApplication.getAppContext());
            Realm realmObj =  realmProcessor.getRealm();

            //ERROR OVER HERE
            Object obj = realmObj.where(ExplorerFolderData.class)
                    .equalTo(Constants.DBPARENTID,"0")
                    .or()
                    .equalTo(Constants.DBPARENTID,"-1")
                    .findAllAsync();
                    return true;
        }

}

I am getting realm object using the same line in Activity as well as JsonProcessor class. What is the mistake I am making over here.

Community
  • 1
  • 1
Sreehari
  • 5,621
  • 2
  • 25
  • 59

2 Answers2

3

The way you are set up with the singleton you have only 1 Realm instance.

If you call realmProcessor.getRealm(); in thread A, and then call it again in thread B, they get back the same instance. Realm does not allow sharing instances between threads. Since the AsyncTask's doInBackground runs on a separate thread, this is not working.

Changing to this will rid you of the error. However you have some redesigning to do.

@Override
protected Boolean doInBackground(Void... params) {
    Realm realmObj =  Realm.getDefaultInstance();

    try {
        Object obj = realmObj.where(ExplorerFolderData.class)
            .equalTo(Constants.DBPARENTID,"0")
            .or()
            .equalTo(Constants.DBPARENTID,"-1")
            .findAll();
    } catch (Exception ex) {
        // handle error
    } finally {
        realmObj.close();
    }

    return true;
}

Note that you are responsible for each and every realm instance. This means that you must manually ensure that you close every instance once you're done with it. A common practice for AsyncTasks is that you wrap your doInBackground operations in a try/catch/finally and close the realm instance in the finally block to ensure it gets closed.

See more in the docs

Sreehari
  • 5,621
  • 2
  • 25
  • 59
Tim
  • 41,901
  • 18
  • 127
  • 145
  • Okey, I will try it out. Can you pls suggest what redesigning should be done as per you , for this flow. – Sreehari Aug 30 '16 at 15:26
  • @Stallion do not use a singleton for realm instances. Just use `.getDefaultInstance()` everywhere you need a realm instance. – Tim Aug 30 '16 at 15:29
  • 1
    Opening a Realm instance in `doInBackground()` and not closing it in `finally {` is bad karma – EpicPandaForce Aug 30 '16 at 15:43
  • 1
    Btw `findAllAsync()` won't work because it's on a non-looper background thread, so there is no handler for it to notify that the async query has been successfully completed, and he should use `findAll()` – EpicPandaForce Aug 30 '16 at 16:30
2

The problem is that your Realm object is created only once on first call to RealmProcessor.with (because it is singleton).Lets say that JsonProcessor::getThingList happen on Thread#1 and ExplorerDBOperation::doInBackground() happens on another Thread#2. So if JsonProcessor::getThingList call will be prior to ExplorerDBOperation::doInBackground() then Realm object will be bound to Thread#1, and when you try to access it from Thread#2 you will get that error.

j2ko
  • 2,479
  • 1
  • 16
  • 29