Tiempo estimado para completar: 30 minutos, dependiendo de su experiencia con Flutter.
El SDK de dispositivos Atlas para Flutter permite crear aplicaciones multiplataforma con Dart y Flutter. Este tutorial se basa en la aplicación de plantilla de sincronización flexible de Flutter. flutter.todo.flex, que ilustra la creación de una aplicación de tareas pendientes. Esta aplicación permite a los usuarios:
Registrar su correo electrónico como una nueva cuenta de usuario.
Sign in to their account with their email and password (and sign out later).
Ver, crear, modificar y eliminar elementos de tarea.
View all tasks, even where the user is not the owner.
La aplicación de plantilla también proporciona un interruptor que simula el dispositivo en modo sin conexión. Este interruptor permite probar rápidamente la sincronización del dispositivo, simulando que el usuario no tiene conexión a internet. Sin embargo, es probable que este interruptor se elimine en una aplicación de producción.
Este tutorial complementa la aplicación de plantilla. Agregará un nuevo priority campo al Item modelo existente y actualizará la suscripción de Sincronización Flexible para mostrar solo elementos dentro de un rango de prioridades.
Objetivos de aprendizaje
Este tutorial ilustra cómo adaptar la aplicación de plantilla a sus necesidades. Dada la estructura actual de la aplicación, no es necesario realizar este cambio.
En este tutorial aprenderás a:
Update a Realm object model with a non-breaking change.
Actualizar una suscripción de sincronización de dispositivos
Add a queryable field to the Device Sync configuration on the server to change which data is synchronized.
Si prefieres comenzar con tu propia aplicación en lugar de seguir un tutorial guiado, consulta la Guía deinicio rápido de Flutter. Incluye ejemplos de código copiables y la información esencial necesaria para configurar una aplicación con el SDK de Flutter.
Requisitos previos
Debes tener experiencia previa en la implementación de una aplicación Flutter en un emulador de Android, un simulador de iOS y/o un dispositivo físico.
Este tutorial comienza con una aplicación de Plantilla. Necesitas una Cuenta de Atlas, una clave API y App Services CLI para crear una aplicación de plantilla.
Para obtener más información sobre cómo crear una cuenta de Atlas, consulte la sección Primeros pasos con Atlas. Para este tutorial, necesita una cuenta de Atlas con un clúster de nivel gratuito.
También necesita una clave API de Atlas para la cuenta de MongoDB Cloud con la que desea iniciar sesión. Debe ser propietario del proyecto para crear una aplicación de plantilla mediante la CLI de App Services.
Para aprender más sobre cómo instalar App Services CLI, consulta Instalar App Services CLI. Después de instalar, ejecuta el comando login utilizando la clave API de tu proyecto Atlas.
Nota
Plataformas compatibles
Puedes compilar esta aplicación de tutorial en las siguientes plataformas:
iOS
Android
macOS
Windows
Linux
El SDK de Flutter no admite la creación de aplicaciones web.
Comience con la plantilla
Este tutorial se basa en la aplicación de plantilla de sincronización flexible de Flutter llamada flutter.todo.flex. Comenzamos con la aplicación predeterminada y creamos nuevas funciones a partir de ella.
Para obtener más información sobre las aplicaciones de plantilla, consulte Aplicaciones de plantilla.
Si aún no tiene una cuenta Atlas, regístrese para implementar una aplicación de plantilla.
Siga el procedimiento descrito en la guía Crear una aplicación de servicios de aplicaciones y seleccione Create App from TemplateSeleccione la plantilla Real-time Sync. Esto crea una aplicación de Servicios de Aplicaciones preconfigurada para usarla con uno de los clientes de la plantilla de Sincronización de Dispositivos.
Después de crear una aplicación plantilla, la Interfaz de Usuario muestra un modal etiquetado como Get the Front-end Code for your Template. Este modal proporciona instrucciones para descargar el código cliente de la aplicación plantilla como un archivo .zip o para usar App Services CLI y obtener el cliente.
Después de seleccionar el método .zip o la CLI de App Services, siga las instrucciones en pantalla para obtener el código de cliente. Para este tutorial, seleccione el código de cliente Dart (Flutter).
Descomprime la aplicación descargada y verás la aplicación Flutter.
Nota
La utilidad ZIP predeterminada de Windows podría mostrar el archivo .zip vacío. Si esto ocurre, use un programa de compresión de terceros disponible.
El comando de creación de aplicaciones appservices configura el backend y crea una aplicación de plantilla Flutter para que la uses como base para este tutorial.
Ejecute el siguiente comando en una ventana de terminal para crear una aplicación llamada "MyTutorialApp" que se implementa en la región US-VA con su entorno configurado en "desarrollo" (en lugar de producción o control de calidad).
appservices app create \ --name MyTutorialApp \ --template flutter.todo.flex \ --deployment-model global \ --environment development
El comando crea un nuevo directorio en su ruta actual con el mismo nombre que el valor del indicador --name.
Puedes bifurcar y clonar un repositorio de GitHub que contenga el código del cliente de sincronización de dispositivos. El código del cliente de Flutter está disponible en https://github.com/mongodb/template-app-dart-flutter-todo.
Si usa este proceso para obtener el código del cliente, debe crear una aplicación de plantilla para usarla con el cliente. Siga las instrucciones de Crear una aplicación de plantilla para usar la interfaz de usuario de Atlas App Services, la CLI de App Services o la API de administración para crear una aplicación de plantilla de backend de Device Sync.
Clona el repositorio y sigue las instrucciones en README para agregar el ID de la aplicación de backend a la aplicación Flutter.
Set up the Template App
Abre la aplicación
Abra la aplicación de Flutter en su editor de código.
Si descargaste el cliente como archivo .zip o clonaste su repositorio de GitHub, debes insertar manualmente el ID de la aplicación de App Services en el lugar correspondiente del cliente. Sigue las instrucciones Configuration del cliente README.md para saber dónde insertar el ID de la aplicación.
Explorar la estructura de la aplicación
En tu editor de código, dedica unos minutos a explorar la organización del proyecto. Se trata de una aplicación Flutter multiplataforma estándar, modificada para nuestro uso específico. En concreto, los siguientes archivos contienen usos importantes del SDK de Flutter:
Archivo | Propósito |
|---|---|
| Punto de entrada a la aplicación. Contiene enrutamiento y gestión de estado. |
| Define el esquema de la base de datos Realm. |
| Clase de objeto Realm generada. |
| Gestiona la interacción con Atlas App Services. |
| Gestiona la interacción con Realm base de datos y Atlas Device Sync. |
| Componentes de una aplicación caracterizada por widgets de Flutter. |
| Páginas de la aplicación. |
Ejecutar la aplicación
Sin realizar ningún cambio en el código, debería poder ejecutar la aplicación en el emulador de Android, el simulador de iOS, el dispositivo móvil físico o el emulador de escritorio.
Primero, instale todas las dependencias ejecutando lo siguiente en su terminal:
flutter pub get
Luego, conéctelo a un dispositivo y ejecute la aplicación Flutter.
Una vez que la aplicación esté ejecutándose, registre una nueva cuenta de usuario y luego agregue un nuevo elemento a su lista de tareas pendientes.
Tip
For more information on running a Flutter app with development tools, refer to the Flutter Test Drive documentation.
Comprueba el Backend
Inicie sesión en MongoDB Atlas. En la pestaña, haga Data Services clic Browse Collections en. En la lista de bases de datos, busque y expanda la todo base de datos y, a continuación, la Item colección. Debería ver el documento que creó en esta colección.
Modificar la aplicación
Agregar una nueva propiedad
Agregar una nueva propiedad al modelo
Now that you have confirmed everything is working as expected, we can add changes. In this tutorial, we have decided that we want to add a "priority" property to each Item so that we can filter Items by their priorities.
Para ello siga estos pasos:
En el proyecto
flutter.todo.flex, abra el archivolib/realm/schemas.dart.Agregue la siguiente propiedad a la clase
_Item:lib/realm/schemas.dartlate int? priority; Regenerate the
ItemRealm object class:dart run realm generate
Set the Priority when Creating and Updating Items
En
lib/realm/realm_services.dart, agrega lógica para establecer y actualizarpriority. También agrega una clase abstractaPriorityLeveldebajo de la claseRealmServicespara restringir los valores posibles.lib/realm/realm_services.dart// ... imports class RealmServices with ChangeNotifier { static const String queryAllName = "getAllItemsSubscription"; static const String queryMyItemsName = "getMyItemsSubscription"; bool showAll = false; bool offlineModeOn = false; bool isWaiting = false; late Realm realm; User? currentUser; App app; // ... RealmServices initializer and updateSubscriptions(), // sessionSwitch() and switchSubscription() methods void createItem(String summary, bool isComplete, int? priority) { final newItem = Item(ObjectId(), summary, currentUser!.id, isComplete: isComplete, priority: priority); realm.write<Item>(() => realm.add<Item>(newItem)); notifyListeners(); } void deleteItem(Item item) { realm.write(() => realm.delete(item)); notifyListeners(); } Future<void> updateItem(Item item, {String? summary, bool? isComplete, int? priority}) async { realm.write(() { if (summary != null) { item.summary = summary; } if (isComplete != null) { item.isComplete = isComplete; } if (priority != null) { item.priority = priority; } }); notifyListeners(); } Future<void> close() async { if (currentUser != null) { await currentUser?.logOut(); currentUser = null; } realm.close(); } void dispose() { realm.close(); super.dispose(); } } abstract class PriorityLevel { static int severe = 0; static int high = 1; static int medium = 2; static int low = 3; } Add a new file to contain a widget to set the priority for an Item. Create the file
lib/components/select_priority.dart.lib/components/select_priority.dartimport 'package:flutter/material.dart'; import 'package:flutter_todo/realm/realm_services.dart'; class SelectPriority extends StatefulWidget { int priority; void Function(int priority) setFormPriority; SelectPriority(this.priority, this.setFormPriority, {Key? key}) : super(key: key); State<SelectPriority> createState() => _SelectPriorityState(); } class _SelectPriorityState extends State<SelectPriority> { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Priority'), DropdownButtonFormField<int>( onChanged: ((int? value) { final valueOrDefault = value ?? PriorityLevel.low; widget.setFormPriority(valueOrDefault); setState(() { widget.priority = valueOrDefault; }); }), value: widget.priority, items: [ DropdownMenuItem( value: PriorityLevel.low, child: const Text("Low")), DropdownMenuItem( value: PriorityLevel.medium, child: const Text("Medium")), DropdownMenuItem( value: PriorityLevel.high, child: const Text("High")), DropdownMenuItem( value: PriorityLevel.severe, child: const Text("Severe")), ], ), ], ), ); } } Ahora, añade el widget
SelectPrioritya los widgetsCreateItemyModifyItem. También necesitas añadir lógica adicional para gestionar la configuración de la prioridad. El código que debes añadir se muestra a continuación.Algunas secciones de los archivos que estás agregando se reemplazan con comentarios en los siguientes ejemplos de código para enfocarse en las secciones de código relevantes que se modifican.
Edita el widget
CreateItemFormenlib/components/create_item.dart:lib/componentes/create_item.dart// ... other imports import 'package:flutter_todo/components/select_priority.dart'; // ... CreateItemAction widget class CreateItemForm extends StatefulWidget { const CreateItemForm({Key? key}) : super(key: key); createState() => _CreateItemFormState(); } class _CreateItemFormState extends State<CreateItemForm> { final _formKey = GlobalKey<FormState>(); late TextEditingController _itemEditingController; int _priority = PriorityLevel.low; void _setPriority(int priority) { setState(() { _priority = priority; }); } // ... initState() and dispose() @override functions Widget build(BuildContext context) { TextTheme theme = Theme.of(context).textTheme; return formLayout( context, Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[ // ... Text and TextFormField widgets SelectPriority(_priority, _setPriority), // ... Padding widget ], ), )); } void save(RealmServices realmServices, BuildContext context) { if (_formKey.currentState!.validate()) { final summary = _itemEditingController.text; realmServices.createItem(summary, false, _priority); Navigator.pop(context); } } } Edita el widget
ModifyItemFormenlib/components/modify_item.dart:lib/components/modify_item.dart// ... other imports import 'package:flutter_todo/components/select_priority.dart'; class ModifyItemForm extends StatefulWidget { final Item item; const ModifyItemForm(this.item, {Key? key}) : super(key: key); _ModifyItemFormState createState() => _ModifyItemFormState(item); } class _ModifyItemFormState extends State<ModifyItemForm> { final _formKey = GlobalKey<FormState>(); final Item item; late TextEditingController _summaryController; late ValueNotifier<bool> _isCompleteController; late int? _priority; void _setPriority(int priority) { setState(() { _priority = priority; }); } _ModifyItemFormState(this.item); void initState() { _summaryController = TextEditingController(text: item.summary); _isCompleteController = ValueNotifier<bool>(item.isComplete) ..addListener(() => setState(() {})); _priority = widget.item.priority; super.initState(); } void dispose() { _summaryController.dispose(); _isCompleteController.dispose(); super.dispose(); } Widget build(BuildContext context) { TextTheme myTextTheme = Theme.of(context).textTheme; final realmServices = Provider.of<RealmServices>(context, listen: false); return formLayout( context, Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[ // ... Text and TextFormField widgets SelectPriority(_priority ?? PriorityLevel.medium, _setPriority), // ... StatefulBuilder widget Padding( padding: const EdgeInsets.only(top: 15), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ cancelButton(context), deleteButton(context, onPressed: () => delete(realmServices, item, context)), okButton(context, "Update", onPressed: () async => await update( context, realmServices, item, _summaryController.text, _isCompleteController.value, _priority)), ], ), ), ], ))); } Future<void> update(BuildContext context, RealmServices realmServices, Item item, String summary, bool isComplete, int? priority) async { if (_formKey.currentState!.validate()) { await realmServices.updateItem(item, summary: summary, isComplete: isComplete, priority: priority); Navigator.pop(context); } } void delete(RealmServices realmServices, Item item, BuildContext context) { realmServices.deleteItem(item); Navigator.pop(context); } } Ahora añade un indicador visual de prioridad en el widget
ItemCardenlib/components/todo_item.dart. Crea un nuevo widget_PriorityIndicatorque brinde un indicador visual de la prioridad del ítem.Añade un widget
_PriorityIndicatorque acabas de crear al widgetTodoItem.lib/components/todo_item.dart// ... imports enum MenuOption { edit, delete } class TodoItem extends StatelessWidget { final Item item; const TodoItem(this.item, {Key? key}) : super(key: key); Widget build(BuildContext context) { final realmServices = Provider.of<RealmServices>(context); bool isMine = (item.ownerId == realmServices.currentUser?.id); return item.isValid ? ListTile( // ... leading property and child content title: Row( children: [ Padding( padding: const EdgeInsets.only(right: 8.0), child: _PriorityIndicator(item.priority), ), SizedBox(width: 175, child: Text(item.summary)), ], ), // ... subtitle, trailing, and shape properties with child content ) : Container(); } // ... handleMenuClick() function } class _PriorityIndicator extends StatelessWidget { final int? priority; const _PriorityIndicator(this.priority, {Key? key}) : super(key: key); Widget getIconForPriority(int? priority) { if (priority == PriorityLevel.low) { return const Icon(Icons.keyboard_arrow_down, color: Colors.blue); } else if (priority == PriorityLevel.medium) { return const Icon(Icons.circle, color: Colors.grey); } else if (priority == PriorityLevel.high) { return const Icon(Icons.keyboard_arrow_up, color: Colors.orange); } else if (priority == PriorityLevel.severe) { return const Icon( Icons.block, color: Colors.red, ); } else { return const SizedBox.shrink(); } } Widget build(BuildContext context) { return getIconForPriority(priority); } }
Ejecutar y probar
Antes de volver a ejecutar la aplicación, realice un reinicio en caliente. Esto garantiza que la sesión de sincronización se reinicie con el nuevo esquema y evita errores de sincronización.
Luego, Iniciar sesión usando la cuenta que creaste anteriormente en este tutorial. Verás el único elemento que creaste anteriormente. Agrega un nuevo elemento, y verás que ahora puedes establecer la prioridad. Elija High para la prioridad y guarde el elemento.
Ahora vuelve a la página de datos de Atlas en tu navegador y actualiza la colección Item. Ahora deberías ver el nuevo ítem con el campo priority añadido y configurado en 1. También notarás que el Item existente ahora también tiene un campo priority, y está configurado en null, como se muestra en la siguiente captura de pantalla:

Nota
¿Por qué no se rompió esta sincronizar?
Adding a property to a Realm object is not a breaking change and therefore does not require a client reset. The template app has Development Mode enabled, so changes to the client Realm object are reflected in the server-side schema. For more information, see Development Mode and Update Your Data Model.
Cambiar la suscripción
Now that we added the priority field, we want to update the Device Sync subscription to only sync Items marked as a High or Severe priority.
Update the subscription
En el archivo lib/realm/realm_services.dart, definimos la suscripción de Sincronización Flexible que define qué documentos sincronizamos con el dispositivo y la cuenta del usuario. Actualmente, sincronizamos todos los documentos cuya propiedad owner coincide con el usuario autenticado.
La suscripción actual:
Future<void> updateSubscriptions() async { realm.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.clear(); if (showAll) { mutableSubscriptions.add(realm.all<Item>(), name: queryAllName); } else { mutableSubscriptions.add( realm.query<Item>(r'owner_id == $0', [currentUser?.id]), name: queryMyItemsName); } }); await realm.subscriptions.waitForSynchronization(); }
Now we're going to change the subscription to only sync High and Severe priority Items. As you may recall, the priority field is of type int, where the highest priority ("Severe") has a value of 0, and the lowest priority ("Low") has a value of 3.
We can make direct comparisons between an int and the priority property. To do so, we're going to refactor the subscription query to include Items where the priority is less than or equal to PriorityLevel.high (or 1). We will also give the subscription the new name "getMyHighPriorityItemsSubscription".
Actualiza la suscripción para borrar la suscripción antigua y crear una nueva que utilice prioridad:
// ... imports class RealmServices with ChangeNotifier { static const String queryAllName = "getAllItemsSubscription"; static const String queryMyItemsName = "getMyItemsSubscription"; static const String queryMyHighPriorityItemsName = "getMyHighPriorityItemsSubscription"; bool showAll = false; bool offlineModeOn = false; bool isWaiting = false; late Realm realm; User? currentUser; App app; RealmServices(this.app) { if (app.currentUser != null || currentUser != app.currentUser) { currentUser ??= app.currentUser; realm = Realm(Configuration.flexibleSync(currentUser!, [Item.schema])); showAll = (realm.subscriptions.findByName(queryAllName) != null); // Check if subscription previously exists on the realm final subscriptionDoesNotExists = (realm.subscriptions.findByName(queryMyHighPriorityItemsName) == null); if (realm.subscriptions.isEmpty || subscriptionDoesNotExists) { updateSubscriptions(); } } } Future<void> updateSubscriptions() async { realm.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.clear(); if (showAll) { mutableSubscriptions.add(realm.all<Item>(), name: queryAllName); } else { mutableSubscriptions.add( realm.query<Item>( r'owner_id == $0 AND priority <= $1', [currentUser?.id, PriorityLevel.high], ), name: queryMyHighPriorityItemsName); } }); await realm.subscriptions.waitForSynchronization(); } // ... other methods }
Ejecutar y probar
Vuelve a ejecutar la aplicación. Inicia sesión usando la cuenta que creaste anteriormente en este tutorial.
After an initial moment when Realm resyncs the document collection, you might see an error message resembling the following:
The following RangeError was thrown building StreamBuilder<RealmResultsChanges<Item>>(dirty, state: _StreamBuilderBaseState<RealmResultsChanges<Item>, AsyncSnapshot<RealmResultsChanges<Item>>>#387c4): RangeError (index): Invalid value: Only valid value is 0: 3
Este error puede ocurrir con el widget StreamBuilder a medida que la suscripción se actualiza. En una aplicación de producción, podrías agregar manejo de errores. Pero para el propósito de este tutorial, simplemente realiza una actualización en caliente y el error desaparecerá.
Ahora deberías ver el nuevo elemento de alta prioridad que creaste.
Tip
Cambiar suscripciones con el modo de desarrollador habilitado
In this tutorial, when you change the subscription and query on the priority field for the first time, the field is automatically added to the Device Sync Collection Queryable Fields. This occurs because the template app has Development Mode enabled by default. If Development Mode was not enabled, you would have to manually add the field as a queryable field to use it in a client-side Sync query.
Para obtener más información, consulte Campos consultables.
Si desea probar la funcionalidad con más detalle, puede crear elementos con diferentes prioridades. Verá que un nuevo elemento con una prioridad menor aparece brevemente en la lista de elementos y luego desaparece. Realm crea el elemento localmente, lo sincroniza con el backend y lo elimina porque no cumple con las reglas de suscripción. Esto se denomina escritura compensatoria.
También, notarás que el documento que creaste inicialmente no está sincronizado porque tiene una prioridad de null. Si quieres que este elemento se sincronice, puedes editar el documento en la Interfaz de Usuario de Atlas y agregar un valor para el campo de prioridad, o puedes cambiar tu suscripción para incluir documentos con valores nulos. También le daremos a la suscripción el nuevo nombre "getUserItemsWithHighOrNoPriority".
class RealmServices with ChangeNotifier { static const String queryAllName = "getAllItemsSubscription"; static const String queryMyItemsName = "getMyItemsSubscription"; static const String queryMyHighPriorityItemsName = "getMyHighPriorityItemsSubscription"; static const String queryMyHighOrNoPriorityItemsName = "getMyHighOrNoPriorityItemsSubscription"; bool showAll = false; bool offlineModeOn = false; bool isWaiting = false; late Realm realm; User? currentUser; App app; RealmServices(this.app) { if (app.currentUser != null || currentUser != app.currentUser) { currentUser ??= app.currentUser; realm = Realm(Configuration.flexibleSync(currentUser!, [Item.schema])); // Check if subscription previously exists on the realm final subscriptionDoesNotExists = realm.subscriptions.findByName(queryMyHighOrNoPriorityItemsName) == null; if (realm.subscriptions.isEmpty || subscriptionDoesNotExists) { updateSubscriptions(); } } } Future<void> updateSubscriptions() async { realm.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.clear(); if (showAll) { mutableSubscriptions.add(realm.all<Item>(), name: queryAllName); } else { mutableSubscriptions.add( realm.query<Item>( r'owner_id == $0 AND priority IN {$1, $2, $3}', [currentUser?.id, PriorityLevel.high, PriorityLevel.severe, null], ), name: queryMyHighPriorityItemsName); } }); await realm.subscriptions.waitForSynchronization(); } // ... other methods }
Nuevamente, cuando se produce un error StreamBuilder la primera vez que abres la aplicación con la nueva suscripción, realiza una actualización rápida para ver los datos esperados.
Conclusión
Agregar una propiedad a un objeto Realm existente es un cambio no disruptivo, y el modo de desarrollo asegura que el cambio de esquema se refleje en el servidor.
¿Qué es lo próximo?
Lee nuestra documentación del Flutter SDK.
Find developer-oriented blog posts and integration tutorials on the MongoDB Developer Hub.
Únase a las comunidades de MongoDB en Reddit o Stack Overflow para aprender de otros desarrolladores y expertos técnicos de MongoDB.
Explore proyectos de ingeniería y ejemplos proporcionados por expertos.
Nota
Share Feedback
¿Cómo te fue? Usa el Rate this page widget en la parte inferior derecha de la página para evaluar su efectividad. O reporta un problema en el repositorio de GitHub si tuviste algún problema.