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 poder reaccionar ante cambios en los datos, independientemente de su origen. Cuando un usuario añade un elemento a una lista, es posible que quieras actualizar la interfaz de usuario, mostrar una notificación o registrar un mensaje. Cuando alguien actualiza ese elemento, es posible que quieras cambiar su estado visual o enviar una solicitud de red. Finalmente, cuando alguien elimina el elemento, probablemente quieras eliminarlo de la interfaz de usuario. El sistema de notificaciones de Realm te permite observar y reaccionar ante los cambios en tus datos, independientemente de las escrituras que los provocaron.
Realm emite tres tipos de notificaciones:
Notificaciones de reino cada vez que un reino específico confirma una transacción de escritura.
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.
Actualización automática
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
Los dominios en un hilo sin Looper no actualizan automáticamente su versión. Esto puede aumentar el tamaño del dominio en memoria y en disco. Evite usar instancias de dominio en hilos sin Looper siempre que sea posible. Si abre un dominio en un hilo sin Looper, ciérrelo al terminar de usarlo.
Registrar un oyente de cambio de reino
Puedes registrar un controlador de notificaciones en todo un dominio. El dominio llama al controlador de notificaciones cada vez que se confirma una transacción de escritura que involucra a ese dominio. El controlador no recibe información sobre el cambio.
Esto es útil cuando se desea saber si ha habido un cambio, pero no se desea saber qué cambió específicamente. Por ejemplo, las aplicaciones de prueba de concepto suelen usar este tipo de notificación y simplemente actualizan toda la interfaz de usuario cuando algo cambia. A medida que la aplicación se vuelve más sofisticada y sensible al rendimiento, los desarrolladores cambian 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
Actualización automática
Todos los hilos que contienen un Looper actualizan automáticamente las instancias RealmObject y RealmResult cuando se escriben nuevos cambios en el dominio. Por lo tanto, no es necesario recuperar esos objetos al reaccionar a un RealmChangeListener, ya que ya están actualizados y listos para ser redibujados en la pantalla.
Registrar un oyente de cambios de colección
Puedes registrar un controlador de notificaciones en una colección específica dentro de un dominio. El controlador recibe una descripción de los cambios desde la última notificación. En concreto, esta descripción consta de tres listas de índices:
Los índices de los objetos que fueron eliminados.
Los índices de los objetos que se insertaron.
Los índices de los objetos que fueron modificados.
Detener la entrega de notificaciones llamando a los métodos removeChangeListener() o removeAllChangeListeners(). Las notificaciones también se detienen si:
El objeto en el que está registrado el oyente es recolectado como basura.
La instancia del reino se cierra.
Mantén una fuerte referencia al objeto que estás escuchando durante el tiempo que necesites las notificaciones.
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 emite una notificación inicial tras recuperar la colección. Posteriormente, envía notificaciones de forma asíncrona cada vez que una transacción de escritura añade, modifica o elimina objetos de la colección.
A diferencia de las notificaciones de dominio, las notificaciones de colección contienen información detallada sobre el cambio. Esto permite reacciones sofisticadas y selectivas a los cambios. Las notificaciones de colección proporcionan toda la información necesaria para gestionar una lista u otra vista que represente la colección en la interfaz de usuario.
El siguiente código muestra cómo observar una colección en busca de cambios con 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 detector de cambios de objetos
Puedes registrar un controlador de notificaciones en un objeto específico dentro de un dominio. El dominio notifica a tu controlador:
Cuando el objeto se borra.
Cuando cambia cualquiera de las propiedades del objeto.
El controlador recibe información sobre qué campos cambiaron y si el objeto fue eliminado.
Detener la entrega de notificaciones llamando a los métodos removeChangeListener() o removeAllChangeListeners(). Las notificaciones también se detienen si:
El objeto en el que está registrado el oyente es recolectado como basura.
La instancia del reino se cierra.
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 reino y observar esa instancia en busca de 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." }
Anular el registro de un detector de cambios
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().
Usar Realm en aplicaciones del sistema en ROM personalizadas
Realm utiliza canalizaciones con nombre para admitir notificaciones y acceder al archivo de realm desde múltiples procesos. Si bien esto está permitido por defecto para las aplicaciones de usuario normales, no está permitido para las aplicaciones del sistema.
Puedes definir una aplicación del sistema configurando android:sharedUserId="android.uid.system" en el manifiesto de Android. Al trabajar con una aplicación del sistema, podrías ver una infracción de seguridad en Logcat similar a esta:
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
Para solucionar esto, debe ajustar las reglas de seguridad de SELinux en la ROM. Esto se puede hacer con la herramienta audit2allow, incluida en AOSP:
Extraiga la política actual del dispositivo:
adb pull /sys/fs/selinux/policy Copie el error de SELinux dentro de un archivo de texto llamado input.txt.
Ejecute la herramienta
audit2allow:audit2allow -p policy -i input.txt La herramienta debería generar una regla que puedas agregar a tu política existente para habilitar el uso de Realm.
A continuación se ofrece un ejemplo de dicha política:
# 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
Cambios en Android Oreo y superiores
Desde Android Oreo, Google cambió la forma de configurar SELinux. Las políticas de seguridad predeterminadas ahora están mucho más modularizadas. Lea más sobre esto aquí.
Límites de notificación de cambios
Los cambios en documentos anidados a más de cuatro niveles de profundidad no activan notificaciones de cambio.
Si tiene una estructura de datos en la que necesita escuchar cambios cinco niveles más abajo o más profundos, las soluciones alternativas incluyen:
Refactorice el esquema para reducir la anidación.
Agregue algo como "push-to-refresh" para permitir que los usuarios actualicen los datos manualmente.