2

From my understanding, Realm can/should only be accessed from the main thread.

I use two realms, one for storing "dirty" data that, upon validation, will be moved to the real realm.

So far so good, but I cannot find a way to do so outside of the mainthread. If I write to the real realm using realm.executeTransactionAsync(), I will not be able to access the dirty realm or its RealmResults inside the transactions thread.

The only workaround is to use dirtyRealm.copyFromRealm() on the main thread - which could potentially block the thread for a longer time, right?

Is this the right approach or are there better solutions?

ferbeb
  • 163
  • 1
  • 2
  • 11
  • `From my understanding, Realm can/should only be accessed from the main thread.` wrong, Realm can only be accessed from the thread that created it. – EpicPandaForce Nov 04 '16 at 13:33
  • @EpicPandaForce That is why I've written should. Without first copying every `RealmObject` into an in-memory copy, I could no longer access them on my views. Would you recommend that? – ferbeb Nov 04 '16 at 13:51
  • I said, you can access the Realm you open on that specific thread. This doesn't mean you cannot access your UI thread Realm on the UI thread. – EpicPandaForce Nov 04 '16 at 14:46

4 Answers4

2

Alternatively to EpicPandaForces answer, one can employ a quick workaround for this problem:

Use a simple synchronous query (like findAll()) and realm.copyFromRealm(results) inside an executeTransactionAsync block - it might not be good practice to execute a transaction that does not include write operations, but it gets the job done without the need to change the whole code.

TLDR; Move your query and copyFromRealm to an executeTransactionAsync block.

ferbeb
  • 163
  • 1
  • 2
  • 11
1

From my understanding, Realm can/should only be accessed from the main thread.

This is a misconception. While Realm auto-updates only on looper threads (such as the main thread), this does not mean you cannot create a new Realm instance on any thread.

If you want to open a Realm on your background thread, you could easily do this:

new Thread(new Runnable() {
    @Override
    public void run() {
        Realm firstRealm = null;
        Realm secondRealm = null;
        try {
           firstRealm = Realm.getInstance(firstConfiguration);
           secondRealm = Realm.getInstance(secondConfiguration);

           firstRealm.beginTransaction();
           secondRealm.beginTransaction();
           RealmResults<SomeObject> someObjects = firstRealm.where(SomeObject.class)
                                                            .equalTo(SomeObjectFields.VALID, true)
                                                            .findAll();
           secondRealm.copyToRealmOrUpdate(someObjects); // I am not sure if you have to detach it first.
           someObjects.deleteAllFromRealm();
           secondRealm.commitTransaction();
           firstRealm.commitTransaction();
        } catch(Throwable e) {
           if(firstRealm != null && firstRealm.isInTransaction()) {
               firstRealm.cancelTransaction();
           }
           if(secondRealm != null && secondRealm.isInTransaction()) {
               secondRealm.cancelTransaction();
           }
           throw e;
        } finally {
           if(firstRealm != null) {
              firstRealm.close();
           }
           if(secondRealm != null) {
              secondRealm.close();
           }
        }
    }
}).start();

And to access the elements on the UI thread, you'd just need a UI thread Realm and a RealmResults with a RealmChangeListener bound to it.

public class MainActivity extends AppCompatActivity {
    Realm realm;

    @BindView(R.id.realm_recycler)
    RecyclerView recyclerView;

    RealmResults<SomeObject> listenerSet;
    RealmChangeListener realmChangeListener = new RealmChangeListener() {
        @Override
        public void onChange(Object element) {
           if(recyclerView != null && recyclerView.getAdapter() != null) {
              recyclerView.getAdapter().notifyDataSetChanged();
           }
        }
    });

    @Override
    public void onCreate(Bundle bundle) {
       super.onCreate(bundle);
       realm = Realm.getDefaultInstance();
       setContentView(R.layout.activity_main);
       ButterKnife.bind(this);

       listenerSet = realm.where(SomeObject.class).findAll();
       listenerSet.addChangeListener(realmChangeListener);

       // set up recyclerView

       adapter.updateData(realm.where(SomeObject.class).findAll());
    }

    @Override
    public void onDestroy() {
       super.onDestroy();
       if(realm != null) {
          realm.close();
          realm = null;
       }
    }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • How do you get the data from the background realm into the main thread though? I think you'd need to create detached copies in the background thread and then somehow notify the main thread to go and fetch them. – Zackline Nov 04 '16 at 17:12
  • Hello? Realm already does that automatically when you commit on a background thread, and notifies the RealmChangeListener? – EpicPandaForce Nov 04 '16 at 17:13
  • You can have different realm instances operate on the same realm database? That would indeed be a life safer here. – Zackline Nov 04 '16 at 17:15
  • Yes. You just need to make sure you close them at the end of execution for the thread, primarily if it's on a background thread. – EpicPandaForce Nov 04 '16 at 17:17
  • Okay, so I'll have to use multiple different realm files since I need to transfer parts of the new data to the "live" realm while still fetching other data into the dirty realm. But I think it will work without any realm I/O inside the main thread, thanks! – Zackline Nov 04 '16 at 17:24
0

There is no standard way of doing that in realm according to this comment by beeender

I think there is no standard way to do this in Realm. I would like to create a static function in RealmObjectA like copyToRealmObjectB which takes two params, one is RealmObjectA and the other is RealmObjectB. And call setters and getters in the static function to do the copy. I am sorry but it doesn't look like a normal and reasonable requirement. Why don't you just use a RealmObject filed instead? See realm.io/docs/java/latest/#field-types

Community
  • 1
  • 1
  • As I understand it, the comment you mentioned is referencing the copying of data from one ClassA extending RealmObject to ClassB extending RealmObject without using Setters and Getters. This is not my issue and I do not think it has anything to do with threading or using multiple `Realm` instances so it is not really of any help for me. – ferbeb Nov 04 '16 at 12:46
0

I used RxJava Completable to complete this operation in background thread

 public class RealmCopier {

    private String errorLog = "";

    public Completable copyTo(Realm realm) {//in my case remote realm on ROS

        return Completable.create(emitter -> {

            boolean isCopied = copy(realm);

            if (!emitter.isDisposed()){

                if (isCopied)
                    emitter.onComplete();
                else
                    emitter.onError(new Throwable(errorLog));

            }
        });

    }

    private boolean copy(Realm realm) {

        try {
            realm.beginTransaction();

            realm.insertOrUpdate(getItems(SomeClassA.class));
            realm.insertOrUpdate(getItems(SomeClassB.class));

            realm.commitTransaction();

        }catch(Exception e){
            realm.cancelTransaction();
            errorLog = e.getMessage();
            return false;

        } finally {
            realm.close();
        }

        return true;
    }

    private List<? extends RealmObject> getItems(Class<? extends RealmObject> classType) {

        RealmConfiguration localConfiguration = ConfigurationManager.createLocalConfiguration();

        Realm realm = Realm.getInstance(localConfiguration);//local realm

        return realm.where(classType).findAll();
    }

}
Pavel Poley
  • 5,307
  • 4
  • 35
  • 66
  • I think you could use `Completable.fromAction` instead of `Completable.create` here – ferbeb Aug 12 '19 at 08:14
  • I think `Completable.create` give you more flexibility ti handle exceptions – Pavel Poley Aug 12 '19 at 08:48
  • You can catch/rethrow in the action. I think you would use create if you want to to signal completion/error from a callback, which you are not, therefore fromAction should be enough – ferbeb Aug 12 '19 at 11:18