Tiempo estimado para completar: 30 minutos, dependiendo de su 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.
Inicie sesión en su cuenta con su correo electrónico y contraseña (y cierre sesión más tarde).
Ver, crear, modificar y eliminar sus propias tareas.
Ver todas las tareas, incluso aquellas en las que el usuario no es el propietario.
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:
Actualice un modelo de objeto de Realm con un cambio ininterrumpido.
Actualizar una suscripción de sincronización de dispositivos.
Agregue un campo consultable a la configuración de sincronización del dispositivo en el servidor para cambiar qué datos se sincronizan.
Nota
Consulte el inicio rápido
Si prefieres empezar con tu propia aplicación en lugar de seguir un tutorial guiado, consulta la Guía de inicio rápido de Kotlin. Incluye ejemplos de código copiables y la información esencial para configurar un backend de Atlas App Services.
Requisitos previos
Android Studio Bumblebee..2021 11 o superior.
JDK 11 o superior.
Complemento Kotlin para Android Studio, versión 1.6.10 o superior.
Un dispositivo virtual Android (AVD) que utiliza una arquitectura de CPU compatible.
Este tutorial comienza con una aplicación de plantilla. Necesita una cuenta de Atlas, una clave API y la CLI de App Services para crearla.
Puede obtener más información sobre cómo crear una cuenta de Atlas en la documentación de introducción a 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 obtener más información sobre la instalación de la CLI de App Services, consulte Instalar la CLI de App Services. Después de la instalación, ejecute el comando de inicio de sesión con la clave API de su 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 de plantilla, la interfaz de usuario muestra un modal con la etiqueta Get the Front-end Code for your Template. Este modal proporciona instrucciones para descargar el código del cliente de la aplicación de plantilla como un archivo .zip o usar la CLI de App Services para 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 de creación de aplicaciones appservices configura el backend y crea una aplicación de plantilla Kotlin 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 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.
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 Kotlin está disponible en https://github.com/mongodb/template-app-kotlin-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 Device Sync.
Configurar la aplicación de plantilla
Abre la aplicación
En Android Studio, abra la carpeta kotlin.todo.flex.
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 |
|---|---|
Actividad de elemento de composición.kt | Clase de actividad que define el diseño y proporciona funcionalidad para abrir un reino, escribir elementos en el reino, cerrar la sesión de un usuario y cerrar un reino. |
ComposeLoginActivity.kt | Clase de actividad que define el diseño y proporciona funcionalidad para registrar e iniciar sesión a un usuario. |
PlantillaApp.kt | Clase que inicializa la aplicación App Services. |
En este tutorial, trabajará con los siguientes archivos:
Archivo | Propósito |
|---|---|
Artículo.kt | Ubicado en el directorio |
Agregar artículo.kt | Ubicado en el directorio |
AgregarItemViewModel.kt | Ubicado en el directorio |
SyncRepository.kt | Ubicado en el directorio |
Cadenas.xml | Ubicado en el |
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 pendientes.
Comprueba el Backend
Inicie sesión en Atlas App Services. 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
Ahora que ha confirmado que todo funciona correctamente, podemos realizar cambios. En este tutorial, hemos decidido añadir una propiedad "priority" a cada elemento para poder filtrarlos por su nivel de prioridad. La propiedad "priority" se asignará a una enumeración PriorityLevel para limitar los valores posibles, y usaremos el ordinal de cada enumeración para que corresponda al entero de prioridad, de modo que podamos realizar consultas basadas en un nivel de prioridad numérico más adelante.
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. Agregue también una propiedadprioritya la claseItem, que establece la prioridad predeterminada en 3, lo que indica que es una tarea pendiente de baja prioridad:dominio/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 agregar un campo desplegable a la función componible
AddItemPromptque permitirá al usuario elegir un nivel de prioridad de una lista usando las enumeraciones 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/valores/cadenas.xml<string name="item_priority">Item Priority</string> Ahora, en la carpeta
presentation/tasks, abra el archivoAddItemViewModel.kt. Aquí agregaremos la lógica de negocio relacionada con nuestro nuevo campo 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
En este punto, puede volver a ejecutar la aplicación. Inicie sesión con la cuenta que creó anteriormente en este tutorial. Verá el elemento que creó anteriormente. Añada un nuevo elemento y verá que ahora puede establecer la prioridad. Seleccione High como prioridad y guarde el elemento.
Ahora, regresa a la página de datos del Atlas en tu navegador y actualiza la Item colección. Deberías ver el nuevo elemento con el priority campo añadido y configurado como. El elemento existente no tiene 1 el priority campo.

Nota
¿Por qué no se rompió esta sincronizar?
Añadir una propiedad a un objeto Realm no supone un cambio drástico y, por lo tanto, no requiere restablecer el cliente. La aplicación de plantilla tiene habilitado el modo de desarrollo, por lo que los cambios en el objeto Realm del cliente se reflejan en el esquema del servidor. Para obtener más información, consulte Modo de desarrollo y Actualizar el modelo de datos.
Cambiar la suscripción
Actualizar la suscripción
En la carpeta app/java/com.mongodb.app/data, abra el archivo SyncRepository.kt, donde definimos la suscripción de Sincronización Flexible. Esta suscripción define qué documentos sincronizamos con el dispositivo y la cuenta del usuario. Busque la función getQuery(). Puede 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() }
También forzaremos la consulta de suscripción para que recalcule qué documentos sincronizar cada vez que abramos la aplicación.
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
Ejecute la aplicación de nuevo. Inicie sesión con la cuenta que creó 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
En este tutorial, al cambiar la suscripción y la consulta en el campo de prioridad por primera vez, este se añade automáticamente a la sincronización de dispositivos Collection Queryable Fields. Esto se debe a que la aplicación de plantilla tiene el modo de desarrollo habilitado de forma predeterminada. Si el modo de desarrollo no estuviera habilitado, tendría que añadir manualmente el campo como consultable para usarlo en una consulta de sincronización del lado del cliente.
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 quieres que este elemento se sincronice, puedes editarlo en la interfaz de usuario de Atlas y agregar un valor al campo de prioridad.
Conclusión
Agregar una propiedad a un objeto Realm existente es un cambio importante y el modo de desarrollo garantiza que el cambio de esquema se refleje en el lado del servidor.
¿Que sigue?
Lea nuestra documentación del SDK de Kotlin.
Encuentre publicaciones de blog orientadas a desarrolladores y tutoriales de integración en 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
Compartir comentarios
¿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.