SKILL: Realm para Flutter (agnóstico al proyecto)
Este documento define reglas y patrones recomendados para usar Realm como capa de persistencia en Flutter, priorizando: consistencia, seguridad, rendimiento, mantenibilidad y testabilidad.
Meta: que el acceso a datos sea predecible: una única fuente de verdad, transacciones claras, modelos bien definidos, migraciones controladas y mínimos “efectos colaterales” en UI.
0) Principios base
0.1 Realm es una base orientada a objetos (OODB)
- Los objetos de Realm suelen ser vivos (live objects): reflejan cambios automáticamente.
- No asumas que un objeto es “inmutable” o que puedes usarlo como DTO fuera de su contexto.
0.2 Single responsibility
- La UI no debe contener lógica de persistencia.
- El acceso a Realm debe pasar por una capa de data source / repository (aunque sea minimalista).
1) Inicialización y ciclo de vida (REQUIRED)
1.1 Un solo punto de construcción de Realm
- REQUIRED: construye la instancia de Realm en un solo lugar.
- REQUIRED: expón esa instancia vía un provider (por ejemplo, Riverpod) o un contenedor de DI.
- FORBIDDEN: crear múltiples instancias “porque sí” en widgets o services.
// ✅ Create and manage Realm lifecycle in one place.
final realmProvider = Provider<Realm>((ref) {
final config = Configuration.local(
[User.schema, Todo.schema],
schemaVersion: 1,
migrationCallback: (migration, oldVersion) {
// English comments preferred.
// Handle migrations here when schemaVersion changes.
},
);
final realm = Realm(config);
ref.onDispose(() {
// ✅ Always close the Realm instance.
realm.close();
});
return realm;
});
1.2 No compartas Realm entre isolates
- REQUIRED: cada isolate debe abrir su propia instancia.
- Si usas background work, pasa IDs/valores primitivos, no instancias de Realm ni objetos Realm.
2) Modelado de datos (schemas) (REQUIRED)
2.1 Convenciones para models
- REQUIRED: define un
primaryKey si el modelo tiene identidad estable.
- REQUIRED: define defaults razonables.
- REQUIRED: evita campos opcionales si el dominio los requiere (prefiere validación en capa de dominio).
@RealmModel()
class _Todo {
@PrimaryKey()
late String id;
late String title;
bool isDone = false;
// Use DateTime for timestamps.
DateTime createdAt = DateTime.now();
}
2.2 Separación “domain vs persistence” (recomendado)
Si tu proyecto requiere independencia de base de datos, considera:
- Entidades de dominio (inmutables)
- Mappers hacia/desde Realm models
Si es un proyecto simple, puedes usar directamente Realm models, pero mantén la lógica fuera de UI.
3) Escrituras y transacciones (REQUIRED)
3.1 Toda escritura debe ir en una transacción
- REQUIRED:
realm.write(() { ... }) (o la variante disponible en tu versión).
- FORBIDDEN: mutar objetos fuera de
write.
void toggleTodo(Realm realm, String todoId) {
final todo = realm.find<Todo>(todoId);
if (todo == null) return;
realm.write(() {
// ✅ Mutations must happen inside a write transaction.
todo.isDone = !todo.isDone;
});
}
3.2 Evita transacciones anidadas
- REQUIRED: estructura tus repositorios para que una operación haga una sola transacción.
- Si un método interno necesita escribir, que reciba una función o se asuma que el caller ya está en write.
3.3 Escrituras por lotes
4) Lecturas, queries y reactividad
4.1 Queries deben ser “reproducibles” y declarativas
- REQUIRED: encapsula queries en repositorios o providers.
- FORBIDDEN: duplicar strings de query por toda la app.
4.2 Colecciones reactivas (streams/notificaciones)
Regla práctica: filtra en la query, no en el widget, cuando el dataset sea mediano/grande.
4.3 Congelar / copiar para “UI segura”
5) Manejo de errores (REQUIRED)
5.1 No ocultes excepciones de persistencia
5.2 Errores comunes a estandarizar
- Violación de primary key / duplicados
- Migración requerida
- Permisos / sync (si aplica)
- Escritura fuera de transacción
6) Migraciones y versionado (REQUIRED)
6.1 Siempre incrementa schemaVersion ante cambios de esquema
- REQUIRED: cualquier cambio a models (campos, tipos, renombres) implica revisar migración.
6.2 Migraciones idempotentes
final config = Configuration.local(
[Todo.schema],
schemaVersion: 3,
migrationCallback: (migration, oldVersion) {
// English comments preferred.
if (oldVersion < 2) {
// Apply changes for v2.
}
if (oldVersion < 3) {
// Apply changes for v3.
}
},
);
6.3 Cambios destructivos
7) Sincronización (si aplica)
Esta sección aplica si usas Realm Sync / App Services. Si no, ignórala.
7.1 Autenticación y sesión
- REQUIRED: encapsula login/logout y el
app.currentUser en un AuthRepository.
- FORBIDDEN: lógica de auth dispersa en pantallas.
7.2 Reglas de acceso y permisos
- No confíes solo en el cliente.
- Define permisos en backend (App Services) y trata errores como parte del flujo.
7.3 Modo offline y conflictos
Define explícitamente:
- qué pasa si no hay red,
- cómo se reintenta,
- cómo se comunican conflictos a UI.
8) Rendimiento y buenas prácticas
8.1 Evita lecturas masivas innecesarias
- Pagina o limita resultados cuando aplique.
- Prefiere queries específicas sobre “traer todo”.
8.2 Índices (si están disponibles en tu SDK)
- Para campos muy consultados, considera indexado (según soporte del SDK).
8.3 No hagas trabajo pesado en el hilo UI
9) Testing (REQUIRED)
9.1 Realm aislado por test
- REQUIRED: cada test debe usar un Realm dedicado (path temporal) para evitar “state leakage”.
- REQUIRED: cerrar realm al final.
Future<Realm> openTestRealm() async {
// English comments preferred.
// Use a temporary directory path in tests.
final config = Configuration.local([Todo.schema]);
return Realm(config);
}
9.2 Repository tests antes que widget tests
Prueba:
- queries,
- migraciones básicas,
- operaciones CRUD,
- reglas de integridad.
10) Anti-patrones (FORBIDDEN)
- FORBIDDEN: abrir/cerrar Realm repetidamente en widgets.
- FORBIDDEN: mutar objetos Realm fuera de una transacción.
- FORBIDDEN: pasar objetos Realm vivos por toda la app como si fueran DTOs.
- FORBIDDEN: queries duplicadas y sin encapsulación.
- FORBIDDEN: migraciones “a mano” sin
schemaVersion.
11) Checklist rápido
1---2name: realm3description: Guide for using Realm in Flutter as a consistent, testable, and maintainable persistence layer, including initialization, schema design, transactions, queries, migrations, sync considerations, error handling, and repository-based architecture. Trigger: Use when the task mentions or contains `Realm`, `Configuration.local`, Realm schemas/models, write transactions, migrations, query encapsulation, Realm providers or DI setup, live Realm objects, sync/session handling, or when deciding how local persistence, reactivity, and data-layer responsibilities should be modeled with Realm in a Flutter app.4---56# SKILL: Realm para Flutter (agnóstico al proyecto)78Este documento define reglas y patrones recomendados para usar **Realm** como capa de persistencia en Flutter, priorizando: **consistencia**, **seguridad**, **rendimiento**, **mantenibilidad** y **testabilidad**.910> **Meta:** que el acceso a datos sea predecible: una única fuente de verdad, transacciones claras, modelos bien definidos, migraciones controladas y mínimos “efectos colaterales” en UI.1112---1314## 0) Principios base1516### 0.1 Realm es una base orientada a objetos (OODB)1718* Los objetos de Realm suelen ser **vivos** (live objects): reflejan cambios automáticamente.19* No asumas que un objeto es “inmutable” o que puedes usarlo como DTO fuera de su contexto.2021### 0.2 Single responsibility2223* La UI **no debe** contener lógica de persistencia.24* El acceso a Realm debe pasar por una capa de **data source / repository** (aunque sea minimalista).2526---2728## 1) Inicialización y ciclo de vida (REQUIRED)2930### 1.1 Un solo punto de construcción de Realm3132* **REQUIRED:** construye la instancia de Realm en un solo lugar.33* **REQUIRED:** expón esa instancia vía un provider (por ejemplo, Riverpod) o un contenedor de DI.34* **FORBIDDEN:** crear múltiples instancias “porque sí” en widgets o services.3536```dart37// ✅ Create and manage Realm lifecycle in one place.38final realmProvider = Provider<Realm>((ref) {39 final config = Configuration.local(40 [User.schema, Todo.schema],41 schemaVersion: 1,42 migrationCallback: (migration, oldVersion) {43 // English comments preferred.44 // Handle migrations here when schemaVersion changes.45 },46 );4748 final realm = Realm(config);4950 ref.onDispose(() {51 // ✅ Always close the Realm instance.52 realm.close();53 });5455 return realm;56});57```5859### 1.2 No compartas Realm entre isolates6061* **REQUIRED:** cada isolate debe abrir su propia instancia.62* Si usas background work, pasa **IDs/valores primitivos**, no instancias de Realm ni objetos Realm.6364---6566## 2) Modelado de datos (schemas) (REQUIRED)6768### 2.1 Convenciones para models6970* **REQUIRED:** define un `primaryKey` si el modelo tiene identidad estable.71* **REQUIRED:** define defaults razonables.72* **REQUIRED:** evita campos opcionales si el dominio los requiere (prefiere validación en capa de dominio).7374```dart75@RealmModel()76class _Todo {77 @PrimaryKey()78 late String id;7980 late String title;81 bool isDone = false;8283 // Use DateTime for timestamps.84 DateTime createdAt = DateTime.now();85}86```8788### 2.2 Separación “domain vs persistence” (recomendado)8990* Si tu proyecto requiere independencia de base de datos, considera:9192 * Entidades de dominio (inmutables)93 * Mappers hacia/desde Realm models94* Si es un proyecto simple, puedes usar directamente Realm models, pero mantén la lógica fuera de UI.9596---9798## 3) Escrituras y transacciones (REQUIRED)99100### 3.1 Toda escritura debe ir en una transacción101102* **REQUIRED:** `realm.write(() { ... })` (o la variante disponible en tu versión).103* **FORBIDDEN:** mutar objetos fuera de `write`.104105```dart106void toggleTodo(Realm realm, String todoId) {107 final todo = realm.find<Todo>(todoId);108 if (todo == null) return;109110 realm.write(() {111 // ✅ Mutations must happen inside a write transaction.112 todo.isDone = !todo.isDone;113 });114}115```116117### 3.2 Evita transacciones anidadas118119* **REQUIRED:** estructura tus repositorios para que una operación haga una sola transacción.120* Si un método interno necesita escribir, que reciba una función o se asuma que el caller ya está en write.121122### 3.3 Escrituras por lotes123124* Si insertas/actualizas muchos elementos:125126 * usa una sola transacción,127 * evita loops con transacciones repetidas.128129---130131## 4) Lecturas, queries y reactividad132133### 4.1 Queries deben ser “reproducibles” y declarativas134135* **REQUIRED:** encapsula queries en repositorios o providers.136* **FORBIDDEN:** duplicar strings de query por toda la app.137138### 4.2 Colecciones reactivas (streams/notificaciones)139140* Cuando necesites UI reactiva:141142 * expón la colección (Results/RealmList) y/o un stream de cambios.143 * asegúrate de no filtrar/transformar de forma costosa en cada rebuild.144145> Regla práctica: filtra en la query, no en el widget, cuando el dataset sea mediano/grande.146147### 4.3 Congelar / copiar para “UI segura”148149* Como los objetos son vivos, si necesitas:150151 * pasar datos a otra capa,152 * evitar que cambien durante un frame,153 * serializar,154 entonces considera:155 * `freeze()` (si tu SDK lo soporta), o156 * mapear a DTO inmutable.157158---159160## 5) Manejo de errores (REQUIRED)161162### 5.1 No ocultes excepciones de persistencia163164* Si falla una escritura o query, debe:165166 * propagarse como error controlado (por ejemplo, `Result`/`Either`), o167 * mapearse a una excepción de dominio.168169### 5.2 Errores comunes a estandarizar170171* Violación de primary key / duplicados172* Migración requerida173* Permisos / sync (si aplica)174* Escritura fuera de transacción175176---177178## 6) Migraciones y versionado (REQUIRED)179180### 6.1 Siempre incrementa `schemaVersion` ante cambios de esquema181182* **REQUIRED:** cualquier cambio a models (campos, tipos, renombres) implica revisar migración.183184### 6.2 Migraciones idempotentes185186* **REQUIRED:** tu `migrationCallback` debe soportar:187188 * usuarios que saltan versiones,189 * ejecución desde `oldVersion` menor a la actual.190191```dart192final config = Configuration.local(193 [Todo.schema],194 schemaVersion: 3,195 migrationCallback: (migration, oldVersion) {196 // English comments preferred.197 if (oldVersion < 2) {198 // Apply changes for v2.199 }200 if (oldVersion < 3) {201 // Apply changes for v3.202 }203 },204);205```206207### 6.3 Cambios destructivos208209* Si un cambio rompe compatibilidad y no hay migración razonable:210211 * documenta el “reset” (borrar realm),212 * y asegúrate de que el usuario no pierda datos críticos (backup/export si aplica).213214---215216## 7) Sincronización (si aplica)217218> Esta sección aplica si usas Realm Sync / App Services. Si no, ignórala.219220### 7.1 Autenticación y sesión221222* **REQUIRED:** encapsula login/logout y el `app.currentUser` en un `AuthRepository`.223* **FORBIDDEN:** lógica de auth dispersa en pantallas.224225### 7.2 Reglas de acceso y permisos226227* No confíes solo en el cliente.228* Define permisos en backend (App Services) y trata errores como parte del flujo.229230### 7.3 Modo offline y conflictos231232* Define explícitamente:233234 * qué pasa si no hay red,235 * cómo se reintenta,236 * cómo se comunican conflictos a UI.237238---239240## 8) Rendimiento y buenas prácticas241242### 8.1 Evita lecturas masivas innecesarias243244* Pagina o limita resultados cuando aplique.245* Prefiere queries específicas sobre “traer todo”.246247### 8.2 Índices (si están disponibles en tu SDK)248249* Para campos muy consultados, considera indexado (según soporte del SDK).250251### 8.3 No hagas trabajo pesado en el hilo UI252253* Transformaciones grandes, exportaciones o cálculos sobre colecciones grandes:254255 * muévelos a un isolate,256 * o reduce el dataset antes de mapear.257258---259260## 9) Testing (REQUIRED)261262### 9.1 Realm aislado por test263264* **REQUIRED:** cada test debe usar un Realm dedicado (path temporal) para evitar “state leakage”.265* **REQUIRED:** cerrar realm al final.266267```dart268Future<Realm> openTestRealm() async {269 // English comments preferred.270 // Use a temporary directory path in tests.271 final config = Configuration.local([Todo.schema]);272 return Realm(config);273}274```275276### 9.2 Repository tests antes que widget tests277278* Prueba:279280 * queries,281 * migraciones básicas,282 * operaciones CRUD,283 * reglas de integridad.284285---286287## 10) Anti-patrones (FORBIDDEN)288289* **FORBIDDEN:** abrir/cerrar Realm repetidamente en widgets.290* **FORBIDDEN:** mutar objetos Realm fuera de una transacción.291* **FORBIDDEN:** pasar objetos Realm vivos por toda la app como si fueran DTOs.292* **FORBIDDEN:** queries duplicadas y sin encapsulación.293* **FORBIDDEN:** migraciones “a mano” sin `schemaVersion`.294295---296297## 11) Checklist rápido298299* [ ] Una sola construcción de Realm y `close()` en dispose.300* [ ] Models con `PrimaryKey` cuando aplique.301* [ ] Escrituras siempre dentro de `write`.302* [ ] Queries encapsuladas (repositorio/providers).303* [ ] Estrategia clara para objetos vivos: freeze o DTO.304* [ ] Migraciones versionadas e idempotentes.305* [ ] Tests con Realm temporal y teardown.306