Los objetos en los clientes de Realm son objetos activos que se actualizan automáticamente para reflejar los cambios de datos, incluidos cambios remotossincronizados y emiten eventos de notificación a los que puede suscribirse cada vez que cambian sus datos subyacentes.
Cualquier aplicación moderna debería ser capaz de reaccionar cuando los datos cambien, sin importar de dónde provengan los cambios. Cuando un usuario agrega un nuevo ítem a una lista, puedes querer actualizar la Interfaz de Usuario, mostrar una notificación o registrar un mensaje. Cuando alguien actualiza ese elemento, es posible que se quiera cambiar su estado visual o ejecutar una solicitud de red. Por último, cuando alguien borra el elemento, probablemente desees removerlo de la Interfaz de Usuario. El sistema de notificaciones de Realm te permite observar y reaccionar a los cambios en tus datos, de manera independiente de las operaciones de guardado que causaron los cambios.
Realm emits three kinds of notifications:
Realm notifications whenever a specific realm commits a write transaction.
Notifications de la colección cada vez que cualquier objeto Realm en una colección cambie, incluidos inserts, actualizaciones y eliminaciones.
Notificaciones de objetos cada vez que un objeto Realm específico cambie, incluidas actualizaciones y eliminaciones.
Auto-Refresh
Objetos de reino a los que se accede en un hilo asociado con un Looper se actualiza automáticamente de forma periódica para reflejar los cambios en los datos subyacentes.
El hilo de la interfaz de usuario de Android siempre contiene un Looper Instancia. Si necesita mantener objetos de Realm durante largos períodos de tiempo en cualquier otro hilo, debe configurar un Looper para ese hilo.
Advertencia
Cierre siempre las instancias de Realm en subprocesos que no sean Looper para evitar fugas de recursos
Realms on a thread without a Looper do not automatically advance their version. This can increase the size of the realm in memory and on disk. Avoid using realm instances on non-Looper threads when possible. If you do open a realm on a non-Looper thread, close the realm when you're done using it.
Register a Realm Change Listener
You can register a notification handler on an entire realm. Realm calls the notification handler whenever any write transaction involving that realm is committed. The handler receives no information about the change.
Esto es útil cuando quieres saber si ha habido un cambio, pero no te interesa saber específicamente qué ha cambiado. Por ejemplo, las aplicaciones de prueba de concepto suelen utilizar este tipo de notificación y simplemente actualizan toda la interfaz de usuario cuando ocurre algún cambio. A medida que la aplicación se vuelve más sofisticada y sensible al rendimiento, los desarrolladores de la aplicación pasan a notificaciones más granulares.
Ejemplo
Supongamos que estás desarrollando una aplicación colaborativa en tiempo real. Para que tu aplicación parezca estar llena de actividad colaborativa, necesitas un indicador que se active al realizar cualquier cambio. En ese caso, un controlador de notificaciones de dominio sería una excelente manera de controlar el código que controla el indicador. El siguiente código muestra cómo observar los cambios en un dominio con addChangeListener():
public class MyActivity extends Activity { private Realm realm; private RealmChangeListener realmListener; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); realm = Realm.getDefaultInstance(); realmListener = new RealmChangeListener<Realm>() { public void onChange(Realm realm) { // ... do something with the updates (UI, etc.) ... } }; // Observe realm notifications. realm.addChangeListener(realmListener); } protected void onDestroy() { super.onDestroy(); // Remove the listener. realm.removeChangeListener(realmListener); // Close the Realm instance. realm.close(); } }
class MyActivity : Activity() { private lateinit var realm: Realm private lateinit var realmListener: RealmChangeListener<Realm> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) realm = Realm.getDefaultInstance() realmListener = RealmChangeListener { // ... do something with the updates (UI, etc.) ... } // Observe realm notifications. realm.addChangeListener(realmListener) } override fun onDestroy() { super.onDestroy() // Remove the listener. realm.removeChangeListener(realmListener) // Close the Realm instance. realm.close() } }
Importante
Automatic Refresh
All threads that contain a Looper automatically refresh RealmObject and RealmResult instances when new changes are written to the realm. As a result, it isn't necessary to fetch those objects again when reacting to a RealmChangeListener, since those objects are already updated and ready to be redrawn to the screen.
Registrar un oyente de cambios de colección
You can register a notification handler on a specific collection within a realm. The handler receives a description of changes since the last notification. Specifically, this description consists of three lists of indices:
Los índices de los objetos que se borraron.
The indices of the objects that were inserted.
The indices of the objects that were modified.
Stop notification delivery by calling the removeChangeListener() or removeAllChangeListeners() methods. Notifications also stop if:
el objeto en el que se registra el oyente se recolecta como basura.
the realm instance closes.
Keep a strong reference to the object you're listening to for as long as you need the notifications.
Importante
El orden importa
En los controladores de notificaciones de colecciones, aplique siempre los cambios en el siguiente orden: eliminaciones, inserciones y modificaciones. Gestionar las inserciones antes de las eliminaciones puede provocar un comportamiento inesperado.
Realm emits an initial notification after retrieving the collection. After that, Realm delivers collection notifications asynchronously whenever a write transaction adds, changes, or removes objects in the collection.
Unlike realm notifications, collection notifications contain detailed information about the change. This enables sophisticated and selective reactions to changes. Collection notifications provide all the information needed to manage a list or other view that represents the collection in the UI.
The following code shows how to observe a collection for changes with addChangeListener():
RealmResults<Dog> dogs = realm.where(Dog.class).findAll(); // Set up the collection notification handler. OrderedRealmCollectionChangeListener<RealmResults<Dog>> changeListener = (collection, changeSet) -> { // For deletions, notify the UI in reverse order if removing elements the UI OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); for (int i = deletions.length - 1; i >= 0; i--) { OrderedCollectionChangeSet.Range range = deletions[i]; Log.v("EXAMPLE", range.length + " dogs deleted at " + range.startIndex); } OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range range : insertions) { Log.v("EXAMPLE", range.length + " dogs inserted at " + range.startIndex); } OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); for (OrderedCollectionChangeSet.Range range : modifications) { Log.v("EXAMPLE", range.length + " dogs modified at " + range.startIndex); } }; // Observe collection notifications. dogs.addChangeListener(changeListener);
val dogs = realm.where(Dog::class.java).findAll() // Set up the collection notification handler. val changeListener = OrderedRealmCollectionChangeListener { collection: RealmResults<Dog>?, changeSet: OrderedCollectionChangeSet -> // For deletions, notify the UI in reverse order if removing elements the UI val deletions = changeSet.deletionRanges for (i in deletions.indices.reversed()) { val range = deletions[i] Log.v("EXAMPLE", "${range.length} dogs deleted at ${range.startIndex}") } val insertions = changeSet.insertionRanges for (range in insertions) { Log.v("EXAMPLE", "${range.length} dogs inserted at ${range.startIndex}") } val modifications = changeSet.changeRanges for (range in modifications) { Log.v("EXAMPLE", "${range.length} dogs modified at ${range.startIndex}") } } // Observe collection notifications. dogs.addChangeListener(changeListener)
Registrar un objeto de escucha de cambios
You can register a notification handler on a specific object within a realm. Realm notifies your handler:
Cuando el objeto se borra.
When any of the object's properties change.
The handler receives information about what fields changed and whether the object was deleted.
Stop notification delivery by calling the removeChangeListener() or removeAllChangeListeners() methods. Notifications also stop if:
el objeto en el que se registra el oyente se recolecta como basura.
the realm instance closes.
Mantén una referencia fuerte del objeto al que estás escuchando durante el tiempo que necesites recibir notificaciones.
El siguiente código muestra cómo crear una nueva instancia de una clase en un realm y observar esa instancia para cambios con addChangeListener():
// Create a dog in the realm. AtomicReference<Dog> dog = new AtomicReference<Dog>(); realm.executeTransaction(transactionRealm -> { dog.set(transactionRealm.createObject(Dog.class, new ObjectId())); dog.get().setName("Max"); }); // Set up the listener. RealmObjectChangeListener<Dog> listener = (changedDog, changeSet) -> { if (changeSet.isDeleted()) { Log.i("EXAMPLE", "The dog was deleted"); return; } for (String fieldName : changeSet.getChangedFields()) { Log.i("EXAMPLE", "Field '" + fieldName + "' changed."); } }; // Observe object notifications. dog.get().addChangeListener(listener); // Update the dog to see the effect. realm.executeTransaction(r -> { dog.get().setName("Wolfie"); // -> "Field 'name' was changed." });
// Create a dog in the realm. var dog = Dog() realm.executeTransaction { transactionRealm -> dog = transactionRealm.createObject(Dog::class.java, ObjectId()) dog.name = "Max" } // Set up the listener. val listener = RealmObjectChangeListener { changedDog: Dog?, changeSet: ObjectChangeSet? -> if (changeSet!!.isDeleted) { Log.i("EXAMPLE", "The dog was deleted") } else { for (fieldName in changeSet.changedFields) { Log.i( "EXAMPLE", "Field '$fieldName' changed." ) } } } // Observe object notifications. dog.addChangeListener(listener) // Update the dog to see the effect. realm.executeTransaction { r: Realm? -> dog.name = "Wolfie" // -> "Field 'name' was changed." }
Unregister a Change Listener
Puedes anular el registro de un detector de cambios pasándolo a Realm.removeChangeListener(). Puedes anular el registro de todos los detectores de cambios suscritos a cambios en un dominio o en cualquiera de sus objetos o colecciones vinculados con Realm.removeAllChangeListeners().
Use Realm in System Apps on Custom ROMs
Realm uses named pipes in order to support notifications and access to the realm file from multiple processes. While this is allowed by default for normal user apps, it is disallowed for system apps.
You can define a system apps by setting android:sharedUserId="android.uid.system" in the Android manifest. When working with a system app, you may see a security violation in Logcat that looks something like this:
05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:99): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0 05-24 14:08:08.984 6921 6921 W .realmsystemapp: type=1400 audit(0.0:100): avc: denied { write } for name="realm.testapp.com.realmsystemapp-Bfqpnjj4mUvxWtfMcOXBCA==" dev="vdc" ino=14660 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0
In order to fix this you need to adjust the SELinux security rules in the ROM. This can be done by using the tool audit2allow, which ships as part of AOSP:
Pull the current policy from the device:
adb pull /sys/fs/selinux/policy Copy the SELinux error inside a text file called input.txt.
Ejecuta la herramienta
audit2allow:audit2allow -p policy -i input.txt The tool should output a rule you can add to your existing policy to enable the use of Realm.
An example of such a policy is provided below:
# Allow system_app to create named pipes required by Realm # Credit: https://github.com/mikalackis/platform_vendor_ariel/blob/master_oreo/sepolicy/system_app.te allow system_app fuse:fifo_file create; allow system_app system_app_data_file:fifo_file create; allow system_app system_app_data_file:fifo_file { read write }; allow system_app system_app_data_file:fifo_file open;
Tip
auditar2permitir
audit2allow Se produce al compilar AOSP/ROM y solo funciona en Linux. Puedes leer más sobre esto aquí.
Nota
Changes in Android Oreo and Above
Desde Android Oreo, Google cambió la manera en que configura SELinux. Las políticas de seguridad por defecto ahora están mucho más modularizadas. Lee más sobre eso aquí.
Change Notification Limits
Changes in nested documents deeper than four levels down do not trigger change notifications.
If you have a data structure where you need to listen for changes five levels down or deeper, workarounds include:
Refactorice el esquema para reducir la anidación.
Add something like "push-to-refresh" to enable users to manually refresh data.