Docs Menu
Docs Home
/ /
SDK de Java

Reaccionar a los cambios - SDK de Java

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.

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.

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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
realmListener = new RealmChangeListener<Realm>() {
@Override
public void onChange(Realm realm) {
// ... do something with the updates (UI, etc.) ...
}
};
// Observe realm notifications.
realm.addChangeListener(realmListener);
}
@Override
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.

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)

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."
}

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().

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:

  1. Extraiga la política actual del dispositivo:

    adb pull /sys/fs/selinux/policy
  2. Copie el error de SELinux dentro de un archivo de texto llamado input.txt.

  3. Ejecute la herramienta audit2allow:

    audit2allow -p policy -i input.txt
  4. 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í.

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.

Volver

Enhebrado

En esta página