Tuesday, February 20, 2018

Realm and threads

Just created objects could be touched in any thread, framework does not mind.

new AsyncTask<Void, Void, Dummy>() {
    @Override    protected Dummy doInBackground(Void... voids) {
        Dummy dummy = new Dummy("a", "b");
        Realm realm = Realm.getDefaultInstance();
        realm.beginTransaction();
        realm.insertOrUpdate(dummy);
        realm.commitTransaction();

        Debug
                .get()
                .with(
                        this,
                        "THREAD %s %s - %s",
                        Thread.currentThread().getName(),
                        dummy.getKey(),
                        dummy.getValue()
                )
                .log();

        new Thread(new Runnable() {
            @Override            public void run() {
                Debug
                        .get()
                        .with(
                                this,
                                "THREAD %s %s - %s",
                                Thread.currentThread().getName(),
                                dummy.getKey(),
                                dummy.getValue()
                        )
                        .log();
                dummy.setValue("c");
                Realm realm = Realm.getDefaultInstance();
                realm.beginTransaction();
                realm.insertOrUpdate(dummy);
                realm.commitTransaction();
                Debug
                        .get()
                        .with(
                                this,
                                "UPDATED : THREAD %s %s - %s",
                                Thread.currentThread().getName(),
                                dummy.getKey(),
                                dummy.getValue()
                        )
                        .log();

            }
        }).start();
        return dummy;
    }

    @Override    protected void onPostExecute(Dummy dummy) {
        Debug
                .get()
                .with(
                        this,
                        "THREAD %s %s - %s",
                        Thread.currentThread().getName(),
                        dummy.getKey(),
                        dummy.getValue()
                )
                .log();
    }
}.execute();

Output is
THREAD AsyncTask #1 b - a
THREAD main b - a
THREAD Thread-6 b - a
UPDATED : THREAD Thread-6 b - c

Well, let's try to read the database.

new AsyncTask<Void, Void, Dummy>() {
    @Override    protected Dummy doInBackground(Void... voids) {
        Realm realm = Realm.getDefaultInstance();
        RealmQuery<Dummy> query = realm.where(Dummy.class);
        List<Dummy> list = query.findAll();
        Dummy result = null;
        for (Dummy dummy : list){
            result = dummy;
            Debug
                    .get()
                    .with(
                            this,
                            "THREAD %s %s - %s",
                            Thread.currentThread().getName(),
                            dummy.getKey(),
                            dummy.getValue()
                    )
                    .log();

            new Thread(new Runnable() {
                @Override                public void run() {
                    Debug
                            .get()
                            .with(
                                    this,
                                    "THREAD %s %s - %s",
                                    Thread.currentThread().getName(),
                                    dummy.getKey(),
                                    dummy.getValue()
                            )
                            .log();
                    dummy.setValue("c");
                    Realm realm = Realm.getDefaultInstance();
                    realm.beginTransaction();
                    realm.insertOrUpdate(dummy);
                    realm.commitTransaction();
                    Debug
                            .get()
                            .with(
                                    this,
                                    "UPDATED : THREAD %s %s - %s",
                                    Thread.currentThread().getName(),
                                    dummy.getKey(),
                                    dummy.getValue()
                            )
                            .log();

                }
            }).start();
            if (result != null) break;
        }
        return result;
    }

    @Override    protected void onPostExecute(Dummy dummy) {
        Debug
                .get()
                .with(
                        this,
                        "THREAD %s %s - %s",
                        Thread.currentThread().getName(),
                        dummy.getKey(),
                        dummy.getValue()
                )
                .log();
    }
}.execute();

Output is
THREAD AsyncTask #1 b - c
java.lang.IllegalStateException: Realm access from incorrect thread. 
Realm objects can only be accessed on the thread they were created.

OK, lets block onPostExecute.

All the same:
THREAD AsyncTask #1 b - c
java.lang.IllegalStateException: Realm access from incorrect thread.
Realm objects can only be accessed on the thread they were created.

OK, I can see, that you cannot touch RealmObject from different thread. 
Docs are right)))

What to do?
Realm offers AutoValue

Let's try.
Installation:
 Here is abstract DummyNotARealmObject class:
import com.google.auto.value.AutoValue;

@AutoValuepublic abstract class DummyNotARealmObject{

    public static DummyNotARealmObject create(String key, String value) {
        return new AutoValue_DummyNotARealmObject(key, value);
    }

    public abstract String getKey();
    public abstract String getValue();

}

... and modified asyncTask:

new AsyncTask<Void, Void, DummyNotARealmObject>() {
    @Override    protected DummyNotARealmObject doInBackground(Void... voids) {
        Realm realm = Realm.getDefaultInstance();
        RealmQuery<Dummy> query = realm.where(Dummy.class);
        List<Dummy> list = query.findAll();
        final DummyNotARealmObject result;
        for (Dummy dummy : list){
            result = DummyNotARealmObject.create(
                    dummy.getKey(),
                    dummy.getValue()
            );
            Debug
                    .get()
                    .with(
                            this,
                            "THREAD %s %s - %s",
                            Thread.currentThread().getName(),
                            dummy.getKey(),
                            dummy.getValue()
                    )
                    .log();

            new Thread(new Runnable() {
                @Override                public void run() {
                    Debug
                            .get()
                            .with(
                                    this,
                                    "THREAD %s %s - %s",
                                    Thread.currentThread().getName(),
                                    result.getKey(),
                                    result.getValue()
                            )
                            .log();
                }
            }).start();
            return result;
        }
        return null;
    }

    @Override    protected void onPostExecute(DummyNotARealmObject dummy) {
        Debug
                .get()
                .with(
                        this,
                        "THREAD %s %s - %s",
                        Thread.currentThread().getName(),
                        dummy.getKey(),
                        dummy.getValue()
                )
                .log();
    }
}.execute();

Output:
THREAD AsyncTask #1 b - c
THREAD main b - c
THREAD Thread-6 b - c
As you can see, now we can easily get access to our Dummy-object's fields from another thread.
All we need is to create abstract class and declare constructor and getters.
This is not as simple as when we use SQLite but I guess Realm is worth it.

No comments:

Post a Comment