Realm-java: Support result in listener for asynchronous transactions

Created on 5 Dec 2017  路  11Comments  路  Source: realm/realm-java

Goal

In an asynchronous transaction, there is no way to return a result that was computed during the transaction back to the main thread.

Expected Results

Something like that would be nice:

final String nameThatMightExistsOrNot = "test";
realm.executeTransactionAsync(new Realm.Transaction<Long>() { // typed callback listener

    @Override
    public Long execute(Realm bgRealm) {
        User user = realm.where(User.class).equalTo("name", nameThatMightExistsOrNot).findFirst();
        if (user == null) {
            user = bgRealm.createObject(User.class, generateId());
        }                
        user.setName(nameThatMightExistsOrNot);
        return user.getId();
    }

    @Override
    public void onSuccess(Long userId) {
        // Now we have the userId!
    }
}

Actual Results

The thing is that currently there doesn't seem to be any async way to retrieve data, update the Realm depending on that data, and return the result back to the main thread. The problem with doing the check before outside the transaction and save it for later is that there might be another transaction that changes the result of the check.

Steps & Code to Reproduce

Copy+paste from documentation:

realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
        User user = realm.where(User.class).equalTo("name", nameThatMightExistsOrNot).findFirst();
        if (user == null) {
            user = bgRealm.createObject(User.class);
        }                
        user.setName(nameThatMightExistsOrNot);
    }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
        // Here I have no idea what happened. Maybe I need to run some logic
        // only when the user was added, but I couldn't possibly know.
    }
});

Version of Realm and tooling

Realm version(s): 4.1.0
Realm sync feature enabled: no
Android Studio version: latest

Most helpful comment

AtomicReference<String> nameRef = new AtomicReference<>();
realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
        User user = realm.where(User.class).equalTo("name", nameThatMightExistsOrNot).findFirst();
        if (user == null) {
            user = bgRealm.createObject(User.class);
        }                
        user.setName(nameThatMightExistsOrNot);
        nameRef.set(nameThatMightExistsOrNot);
    }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
        // Here I have no idea what happened. Maybe I need to run some logic
        // only when the user was added, but I couldn't possibly know.
         String name = nameRef.get();
    }
});

All 11 comments

You can use something (slightly tacky) like AtomicReference<T>. declared before the executeTransactionAsync

Sounds good but couldn't find any documentation about it. Got an example?

AtomicReference<String> nameRef = new AtomicReference<>();
realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
        User user = realm.where(User.class).equalTo("name", nameThatMightExistsOrNot).findFirst();
        if (user == null) {
            user = bgRealm.createObject(User.class);
        }                
        user.setName(nameThatMightExistsOrNot);
        nameRef.set(nameThatMightExistsOrNot);
    }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
        // Here I have no idea what happened. Maybe I need to run some logic
        // only when the user was added, but I couldn't possibly know.
         String name = nameRef.get();
    }
});

Thanks, this seems to work! Wrapped the whole thing into my own class, now I can do as I suggested.

Realm team, feel free to close, would be nice having this in the API directly though. What would be even more awesome if when returning a RealmObject in the transaction thread, you would automatically refetch it for me in the callback on the main thread. Also, not sure why you need three separate objects instead of just one with three methods for transaction, success and error. Something like that would be really cool:

realm.executeTransactionAsync(new Realm.TransactionWithResult<User>() {
    @Override
    public User execute(Realm bgRealm) {
        User user = bgRealm.where(User.class).equalTo("name", name).findFirst();
        if (user == null) {
            user = bgRealm.createObject(User.class);
        }
        user.setName(name);
        return user; // user on background thread
    }

    @Override
    public void onSuccess(User user) {
        // user here is on from ui thread
    }

    @Override
    public void onError(Throwable error) {
        // treat error here
    }
});

I do admit that the introduction of the ThreadSafeReference which would allow passing RealmObject between thread would be nice, but due to how Java's GC is kinda random, that hasn't been added (and why Realm has .close() methods instead).

By the way, an interesting fact - I actually didn't come up with this completely on my own, I saw the following (currently unused) code in the Kotlin extensions library:

inline fun <T> Realm.callTransaction(crossinline action: Realm.() -> T): T {
     val ref = AtomicReference<T>()
     executeTransaction {
         ref.set(action(it))
     }
     return ref.get()
}

Which basically does what we just did here, except only with synchronous transaction.

Yes, unfortunately there is a very big difference between async and synchronous transactions, so doing it for asynchronous transactions is a lot more involved.

Well what I'm doing now is basically passing the primary key back through the AtomicReference and re-fetching the object on the main thread. Is there no way to do this automatically?

You are doing it correctly with current api

Yes, but having Realm doing it for me would be much more convenient. :)

@freezy we have an issue to track this, see https://github.com/realm/realm-java/issues/1208

Was this page helpful?
0 / 5 - 0 ratings