Tiempo estimado para completar: 30 minutos, según tu experiencia con Kotlin
Realm proporciona un SDK de Kotlin que le permite crear una aplicación móvil Android con Kotlin usando Jetpack ComposeEste tutorial se basa en la aplicación de plantilla de sincronización flexible de Kotlin, llamada kotlin.todo.flex, que ilustra la creación de una aplicación de gestión de listas 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 sus propias tareas.
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 en el simulador, 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 añade funcionalidad a la Aplicación Plantilla. Agregarás un nuevo campo Priority al modelo Item existente y actualizarás la suscripción Flexible Sync para mostrar solo los elementos dentro de un rango de prioridades. Este ejemplo ilustra cómo podrías adaptar la aplicación plantilla para tus propias necesidades.
Objetivos de aprendizaje
Este tutorial ilustra cómo puedes adaptar la aplicación de plantilla a tus propias necesidades.
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.
Nota
Consulte el inicio rápido
If you prefer to get started with your own application rather than follow a guided tutorial, check out the Kotlin Quick Start. It includes copyable code examples and the essential information that you need to set up an Atlas App Services backend.
Requisitos previos
Android Studio Bumblebee 2021.1.1 or higher.
JDK 11 or higher.
Complemento Kotlin para Android Studio, versión 1.6.10 o superior.
An Android Virtual Device (AVD) using a supported CPU architecture.
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.
Comience con la plantilla
Este tutorial se basa en la aplicación de plantilla de sincronización flexible del SDK de Kotlin, denominada kotlin.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 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 Kotlin (Android).
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 appservices apps create configura el backend y crea una aplicación plantilla de Kotlin para usar como base en 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 kotlin.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.
You can fork and clone a GitHub repository that contains the Device Sync client code. The Kotlin client code is available at https://github.com/mongodb/template-app-kotlin-todo.
Si usas este proceso para obtener el código del cliente, debes crear una aplicación de plantilla para usar con el cliente. Sigue las instrucciones en Crear una aplicación de plantilla para usar la Interfaz de Usuario de Atlas App Services, los App Services CLI o la API de Administración para crear una aplicación de plantilla Device Sync.
Set up the Template App
Abre la aplicación
In Android Studio, open the kotlin.todo.flex folder.
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
Dedica unos minutos a explorar la organización del proyecto mientras Android Studio lo indexa. En el directorio app/java/com.mongodb.app, puedes ver algunos archivos importantes:
Archivo | Propósito |
|---|---|
ComposeItemActivity.kt | Clase Activity que define el diseño y proporciona funcionalidad para abrir un realm, escribir elementos en el realm, cerrar sesión de un usuario y cerrar un realm. |
ComposeLoginActivity.kt | Activity class that defines the layout and provides functionality for registering a user and logging a user in. |
TemplateApp.kt | Clase que inicializa la aplicación App Services. |
In this tutorial, you'll be working in the following files:
Archivo | Propósito |
|---|---|
Item.kt | Situado en el directorio |
Agregar artículo.kt | Ubicado en el directorio |
AddItemViewModel.kt | Situado en el directorio |
SyncRepository.kt | Located in the |
Cadenas.xml | Located in the |
Ejecutar la aplicación
Sin realizar ningún cambio en el código, debería poder ejecutar la aplicación en un emulador de Android usando Android Studio o en un dispositivo físico.
Ejecute la aplicación, registre una nueva cuenta de usuario y luego agregue un nuevo elemento a Su lista de tareas.
Comprueba el Backend
Inicia sesión en Atlas App Services. En la pestaña Data Services, haz clic en Browse Collections. En la lista de bases de datos, busca y expande la base de datos todo y luego la colección Item. 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 priority level. The priority property will be mapped to a PriorityLevel enum to constrain the possible values, and we will use the ordinal of each enum to correspond to the priority integer so we can query based on a numeric priority level later.
Para ello siga estos pasos:
Dentro de la carpeta
app/java/com.mongodb.app/domain, abra el archivo de claseItem.Agregue una enumeración
PriorityLevelpara restringir los valores posibles. También agregue una propiedadprioritya la claseItem, que establece la prioridad predeterminada en 3, lo que indica que es una tarea pendiente de baja prioridad:domain/Item.kt// ... imports enum class PriorityLevel() { Severe, // priority 0 High, // priority 1 Medium, // priority 2 Low // priority 3 } class Item() : RealmObject { var _id: ObjectId = ObjectId.create() var isComplete: Boolean = false var summary: String = "" var owner_id: String = "" var priority: Int = PriorityLevel.Low.ordinal constructor(ownerId: String = "") : this() { owner_id = ownerId } // ... equals() and hashCode() functions }
Establecer la prioridad al crear un nuevo elemento
Desde la carpeta
ui/tasks, abra el archivoAddItem.kt. Este archivo define las funciones componibles para la interfaz de usuario que se muestra cuando un usuario hace clic en el botón "+" para añadir una nueva tarea.Primero, agregue las siguientes importaciones debajo de
package com.mongodb.app:ui/tasks/AddItem.ktimport androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.mongodb.app.domain.PriorityLevel Ahora podemos añadir un campo desplegable a la función componible
AddItemPromptque permitirá al usuario elegir un nivel de prioridad de una lista utilizando los enums PriorityLevel como valores disponibles:ui/tasks/AddItem.kt// ... imports fun AddItemPrompt(viewModel: AddItemViewModel) { AlertDialog( containerColor = Color.White, onDismissRequest = { viewModel.closeAddTaskDialog() }, title = { Text(stringResource(R.string.add_item)) }, text = { Column { Text(stringResource(R.string.enter_item_name)) TextField( colors = ExposedDropdownMenuDefaults.textFieldColors(containerColor = Color.White), value = viewModel.taskSummary.value, maxLines = 2, onValueChange = { viewModel.updateTaskSummary(it) }, label = { Text(stringResource(R.string.item_summary)) } ) val priorities = PriorityLevel.values() ExposedDropdownMenuBox( modifier = Modifier.padding(16.dp), expanded = viewModel.expanded.value, onExpandedChange = { viewModel.open() }, ) { TextField( readOnly = true, value = viewModel.taskPriority.value.name, onValueChange = {}, label = { Text(stringResource(R.string.item_priority)) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = viewModel.expanded.value) }, colors = ExposedDropdownMenuDefaults.textFieldColors(), modifier = Modifier .fillMaxWidth() .menuAnchor() ) ExposedDropdownMenu( expanded = viewModel.expanded.value, onDismissRequest = { viewModel.close() } ) { priorities.forEach { DropdownMenuItem( text = { Text(it.name) }, onClick = { viewModel.updateTaskPriority(it) viewModel.close() } ) } } } } }, // ... buttons ) } Android Studio identificará varios errores. Los corregiremos en los próximos pasos añadiendo las funciones correspondientes.
A continuación, definiremos la etiqueta del campo desplegable como un recurso de cadena. Abra el archivo
res/values/strings.xmly agregue lo siguiente antes de cerrar el elemento "resource":res/values/strings.xml<string name="item_priority">Item Priority</string> Ahora, dentro de la carpeta
presentation/tasks, abre el archivoAddItemViewModel.kt. Aquí agregaremos la lógica de negocio relacionada con nuestro nuevo campo de menú desplegable.Agregue la importación
PriorityLeveldebajo depackage com.mongodb.app, luego agregue las variables y funciones a la claseAddItemViewModelnecesarias para manejar los cambios de estado dentro del menú desplegable:presentation/tareas/AddItemViewModel.kt// ... imports import com.mongodb.app.domain.PriorityLevel // ... events class AddItemViewModel( private val repository: SyncRepository ) : ViewModel() { private val _addItemPopupVisible: MutableState<Boolean> = mutableStateOf(false) val addItemPopupVisible: State<Boolean> get() = _addItemPopupVisible private val _taskSummary: MutableState<String> = mutableStateOf("") val taskSummary: State<String> get() = _taskSummary private val _taskPriority: MutableState<PriorityLevel> = mutableStateOf(PriorityLevel.Low) val taskPriority: State<PriorityLevel> get() = _taskPriority private val _expanded: MutableState<Boolean> = mutableStateOf(false) val expanded: State<Boolean> get() = _expanded private val _addItemEvent: MutableSharedFlow<AddItemEvent> = MutableSharedFlow() val addItemEvent: Flow<AddItemEvent> get() = _addItemEvent fun openAddTaskDialog() { _addItemPopupVisible.value = true } fun closeAddTaskDialog() { cleanUpAndClose() } fun updateTaskSummary(taskSummary: String) { _taskSummary.value = taskSummary } fun updateTaskPriority(taskPriority: PriorityLevel) { _taskPriority.value = taskPriority } fun open() { _expanded.value = true } fun close() { _expanded.value = false } // addTask() and cleanUpAndClose() functions } Ahora actualice las funciones
addTask()ycleanUpAndClose()para incluir el nuevo parámetrotaskPriority, actualice el mensaje con la información de prioridad y restablezca el campo de prioridad a bajo una vez que se cierre la vista Agregar elemento:fun addTask() { CoroutineScope(Dispatchers.IO).launch { runCatching { repository.addTask(taskSummary.value, taskPriority.value) }.onSuccess { withContext(Dispatchers.Main) { _addItemEvent.emit(AddItemEvent.Info("Task '$taskSummary' with priority '$taskPriority' added successfully.")) } }.onFailure { withContext(Dispatchers.Main) { _addItemEvent.emit(AddItemEvent.Error("There was an error while adding the task '$taskSummary'", it)) } } cleanUpAndClose() } } private fun cleanUpAndClose() { _taskSummary.value = "" _taskPriority.value = PriorityLevel.Low _addItemPopupVisible.value = false } Finalmente, desde la carpeta
data, abra el archivoSyncRepository.ktpara reflejar los mismos cambios en la funciónaddTask(), que escribe el elemento en el reino.Primero, agregue la importación
PriorityLeveldebajo depackage com.mongodb.app, luego actualice las funcionesaddTask()para pasartaskPrioritycomo parámetro y escriba el campopriorityen el reino como un entero (usando el ordinal de enumeración):datos/SyncRepository.kt// ... imports import com.mongodb.app.domain.PriorityLevel interface SyncRepository { // ... Sync functions suspend fun addTask(taskSummary: String, taskPriority: PriorityLevel) // ... Sync functions } class RealmSyncRepository( onSyncError: (session: SyncSession, error: SyncException) -> Unit ) : SyncRepository { // ... variables and SyncConfiguration initializer // ... Sync functions override suspend fun addTask(taskSummary: String, taskPriority: PriorityLevel) { val task = Item().apply { owner_id = currentUser.id summary = taskSummary priority = taskPriority.ordinal } realm.write { copyToRealm(task) } } override suspend fun updateSubscriptions(subscriptionType: SubscriptionType) { realm.subscriptions.update { removeAll() val query = when (subscriptionType) { SubscriptionType.MINE -> getQuery(realm, SubscriptionType.MINE) SubscriptionType.ALL -> getQuery(realm, SubscriptionType.ALL) } add(query, subscriptionType.name) } } // ... additional Sync functions } class MockRepository : SyncRepository { override fun getTaskList(): Flow<ResultsChange<Item>> = flowOf() override suspend fun toggleIsComplete(task: Item) = Unit override suspend fun addTask(taskSummary: String, taskPriority: PriorityLevel) = Unit override suspend fun updateSubscriptions(subscriptionType: SubscriptionType) = Unit override suspend fun deleteTask(task: Item) = Unit override fun getActiveSubscriptionType(realm: Realm?): SubscriptionType = SubscriptionType.ALL override fun pauseSync() = Unit override fun resumeSync() = Unit override fun isTaskMine(task: Item): Boolean = task.owner_id == MOCK_OWNER_ID_MINE override fun close() = Unit // ... companion object }
Ejecutar y probar
At this point, you can rerun the application. Log in using the account you created earlier in this tutorial. You will see the one Item you previously created. Add a new Item, and you will see that you can now set the priority. Choose High for the priority and save the Item.
Ahora, regrese a la página de datos del Atlas en su navegador y actualice la Item colección. Debería ver el nuevo elemento con el priority campo agregado y configurado en 1. El elemento existente no tiene el priority campo.

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
Update the subscription
Dentro de la carpeta app/java/com.mongodb.app/data, abre el archivo SyncRepository.kt, donde definimos la suscripción Flexible Sync. La suscripción define qué documentos sincronizamos con el dispositivo y la cuenta del usuario. Busca la función getQuery(). Puedes ver que actualmente estamos suscritos a dos suscripciones:
MINE:Todos los documentos donde la propiedadownerIdcoincide con el usuario autenticado.ALL: Todos los documentos de todos los usuarios.
Queremos actualizar la MINE suscripción para sincronizar únicamente los elementos que estén marcados como prioridad alta o grave.
Como recordarás, el campo priority es de tipo int, donde la prioridad más alta ("Grave") tiene un valor de 0 y la prioridad más baja ("Baja") tiene un valor de 3. Podemos hacer comparaciones directas entre un entero y la propiedad de prioridad. Para ello, edita la instrucción RQL para incluir documentos donde la prioridad sea igual o menor que PriorityLevel.High (o 1), como se muestra aquí:
private fun getQuery(realm: Realm, subscriptionType: SubscriptionType): RealmQuery<Item> = when (subscriptionType) { SubscriptionType.MINE -> realm.query("owner_id == $0 AND priority <= ${PriorityLevel.High.ordinal}", currentUser.id) SubscriptionType.ALL -> realm.query() }
We'll also force the subscription query to recalculate which documents to sync every time we open the app.
Para ello, busque la función SyncConfiguration.Builder().initialSubscriptions() que nuestra aplicación llama al inicio. Primero, añada el parámetro reRunOnOpen a true y, a continuación, configure updateExisting a true, lo que permite actualizar la consulta existente.
config = SyncConfiguration.Builder(currentUser, setOf(Item::class)) .initialSubscriptions(rerunOnOpen = true) { realm -> // Subscribe to the active subscriptionType - first time defaults to MINE val activeSubscriptionType = getActiveSubscriptionType(realm) add(getQuery(realm, activeSubscriptionType), activeSubscriptionType.name, updateExisting = true) } .errorHandler { session: SyncSession, error: SyncException -> onSyncError.invoke(session, error) } .waitForInitialRemoteData() .build()
Ejecutar y probar
Vuelve a ejecutar la aplicación. Inicia sesión usando la cuenta que creaste anteriormente en este tutorial.
Después de un momento inicial cuando Realm vuelve a sincronizar la colección de documentos, verá el nuevo elemento de alta prioridad que creó.
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. Observará que si intenta agregar un elemento con una prioridad inferior a Alta, recibirá un mensaje de notificación indicando que no tiene permiso. Si revisa sus registros con Logcat, verá un mensaje indicando que el elemento se agregó correctamente, seguido de un error de sincronización:
ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted"
Esto se debe a que, en este escenario, Realm crea el elemento localmente, lo sincroniza con el backend y luego revierte la escritura porque no cumple con las reglas de suscripción.
También, notarás que el documento que creaste inicialmente no está sincronizado porque tiene una prioridad de null. Si deseas 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.
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?
Lea nuestra documentación del SDK de Kotlin.
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.