Android realm gives outdated results in multithreading scenarios
situation
I have a typical UI thread and worker thread scheme. I do some work and write the results to the fields in the worker thread. The result is a simple realobject with some string fields. After this operation, I send the events on the UI thread to my activity (using Otto event bus) to report that the work has been completed
After receiving an event in my activity, I will query the result, and the string field is not updated with the written value
On worker threads:
// Did some work. Got some result
// Write to realm
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
MyResult result = realm.where(MyResult.class)
.equalTo("id", 1)
.findFirst();
result.someString = "hello world";
}
});
} finally {
if(realm != null){
realm.close();
realm = null;
}
}
//Post job done event on Otto bus
uiThreadBus.post(new JobDoneEvent());
During the activity:
// Upon received JobDoneEvent
MyResult result = realm.where(MyResult.class)
.equalTo("id", 1)
.findFirst();
// result.someString is some stale value
Log.d("TAG", result.someString);
What did I do?
I realized that if I wrap the query in a transaction block, realmobject will be up to date when I try to print the query
// Upon received JobDoneEvent
MyResult result = null;
try{
realm.beginTransaction();
result = realm.where(MyResult.class)
.equalTo("id", 1)
.findFirst();
realm.cancelTransaction();
}
catch(Exception e) {
realm.cancelTransaction();
}
// result.someString is up-to-date
Log.d("TAG", result.someString);
problem
>What is the correct way to get the latest realmobject? Do I have to put them into a transaction block every time to force it to "synchronize" with the worker thread? Is there a pattern to follow? > What exactly does it do to start a domain transaction (through real #begintransaction() or real #executetransaction())? Does it prevent other threads from making read / write attempts? Is there any harm in performing long operations (such as network requests) in transactions?
edit
Actual code:
// Did some work. Got some result
// Write to realm
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
User managedUser = result.payload.createOrUpdateInRealm(realm,
MyApplication.getPrimaryKeyFactory());
Log.i("TAG", "updated user: " + managedUser.getId());
}
});
} finally {
if(realm != null) {
realm.close();
realm = null;
}
}
//Post job done event on Otto bus
MyApplication.getBusInstance().post(new LoginEvent());
// Writing to realm method
public User createOrUpdateInRealm(@NonNull Realm realm,
@NonNull PrimaryKeyFactory pkFactory) {
User managedUser = realm.where(User.class)
.equalTo("primary_key", pk)
.findFirst();
managedUser.setId(xUserId);
return managedUser;
}
// Event receiving method in Activity
@Subscribe
public void loginEventReceived(LoginEvent event) {
User user = mRealm.where(User.class)
.equalTo("primary_key", mPk)
.findFirst();
Log.d("TAG", user.getId());
}
resolvent:
The domain uses a special "listener" thread to communicate with other threads, which puts the message into the looper queue of the UI thread. We will not provide any guarantee, because it may be delayed for many reasons. Otto will send a message directly, which is likely to occur before the loopback message arrives. In this case, the data on the UI thread will be displayed as "stale"
For these types of notifications, it is best to use realm to change the listener. In this case, you will be notified when the data is ready
See also: https://github.com/realm/realm-java/issues/3427