Realm Java and Asynchronous Threading: Frozen Object State Synchronization
0 reputation · 02 Mar 2022, 17:04 UTC
0 reputation · 02 Mar 2022, 17:04 UTC
Integrating Realm Java with an asynchronous threading model requires managing the boundary between thread-confined live objects and thread-safe frozen objects. While frozen objects allow read access across different threads without triggering an IllegalStateException, they represent a snapshot of the data at a specific point in time.
The challenge arises when a background worker thread performs a writeAsync operation to update a record that is currently being observed as a frozen object on the UI thread. Because frozen objects do not automatically update to reflect changes made in subsequent transactions, the UI state may diverge from the persisted disk state until a new frozen snapshot is explicitly obtained.
34775 reputation · 03 Mar 2022, 00:14 UTC
To keep frozen objects in sync after a background writeAsync transaction, attach a RealmChangeListener to the Realm instance (or to the specific RealmResults/RealmObject) on the UI thread. The listener fires on that thread’s Looper once the transaction is committed, allowing you to obtain a fresh frozen snapshot.
realm.addChangeListener { realm, _ ->
// Obtain a new frozen snapshot for the object you are observing
val freshFrozen = realm.where(MyModel::class.java)
.equalTo("id", currentId)
.findFirst()
?.freeze()
// Update UI with freshFrozen
}writeAsync:backgroundRealm.writeAsync { transaction ->
val obj = transaction.where(MyModel::class.java)
.equalTo("id", targetId)
.findFirst()
obj?.field = newValue
}If you already have a frozen object and need to modify it:
id).val live = realm.where(MyModel::class.java)
.equalTo("id", frozenObject.id)
.findFirst()@PrimaryKey), this lookup is O(log n) and avoids a full table scan.RealmResults list), you can skip the lookup and modify the managed object directly inside a transaction.Missing diagnostic detail: Confirm that the UI thread where you add the listener is attached to a Looper (e.g., the main thread or a HandlerThread). If it lacks a Looper, the change listener will not be delivered and you must post to a Looper‑bound thread or use Handler(Looper.getMainLooper()).
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 02 Mar 2022, 23:09 UTC
One clarification that matters for the listener approach: RealmChangeListener cannot be attached to a frozen instance. Registration has to happen on a live Realm, RealmResults or RealmObject owned by a Realm opened on the observing thread; the frozen copy is only the value you render.
That usually means the UI holds two references: a live collection used purely for notifications, and a frozen snapshot used for reads. When the listener fires, replace the frozen reference rather than trying to "unfreeze" it. freeze() is one-way, and a write on a frozen instance throws IllegalStateException.
For rehydration, realm.getObject(MyModel::class.java, pk) returns the live managed object straight from the primary key index, so a query-builder round trip isn't required. Also note that writeAsync completion callbacks are not guaranteed to run on the main looper, so route UI work through the thread's Handler or executor instead of assuming main-thread delivery. Assumes Realm Java 10.x; confirm listener thread delivery with a small instrumented test.