Docs Menu
Docs Home
/ /
Servicios de aplicaciones Atlas

Tutorial: Sincronización de dispositivos Atlas para Kotlin

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.

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.

  • 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 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.

1

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.

2

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 domain. Define el objeto Realm que almacenamos en la base de datos.

Agregar artículo.kt

Ubicado en el directorio ui/tasks. Contiene la función componible que define el diseño utilizado al agregar un elemento.

AgregarItemViewModel.kt

Ubicado en el directorio presentation/tasks. El modelo de vista contiene la lógica de negocio y administra el estado al agregar un elemento.

SyncRepository.kt

Ubicado en el directorio data. Repositorio utilizado para acceder a Realm Sync y que define la suscripción a Flexible Sync.

Cadenas.xml

Ubicado en el res/values directorio. Define los recursos de cadena de texto utilizados en la aplicación.

3

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.

4

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.

1

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:

  1. Dentro de la carpeta app/java/com.mongodb.app/domain, abra el archivo de clase Item.

  2. Agregue una enumeración PriorityLevel para restringir los valores posibles. Agregue también una propiedad priority a la clase Item, 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 {
    @PrimaryKey
    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
    }
2
  1. Desde la carpeta ui/tasks, abra el archivo AddItem.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.

  2. Primero, agregue las siguientes importaciones debajo de package com.mongodb.app:

    ui/tasks/AddItem.kt
    import 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
  3. Ahora podemos agregar un campo desplegable a la función componible AddItemPrompt que permitirá al usuario elegir un nivel de prioridad de una lista usando las enumeraciones PriorityLevel como valores disponibles:

    ui/tasks/AddItem.kt
    // ... imports
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    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.

  4. A continuación, definiremos la etiqueta del campo desplegable como un recurso de cadena. Abra el archivo res/values/strings.xml y agregue lo siguiente antes de cerrar el elemento "resource":

    res/valores/cadenas.xml
    <string name="item_priority">Item Priority</string>
  5. Ahora, en la carpeta presentation/tasks, abra el archivo AddItemViewModel.kt. Aquí agregaremos la lógica de negocio relacionada con nuestro nuevo campo desplegable.

    Agregue la importación PriorityLevel debajo de package com.mongodb.app, luego agregue las variables y funciones a la clase AddItemViewModel necesarias 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() y cleanUpAndClose() para incluir el nuevo parámetro taskPriority, 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
    }
  6. Finalmente, desde la carpeta data, abra el archivo SyncRepository.kt para reflejar los mismos cambios en la función addTask(), que escribe el elemento en el reino.

    Primero, agregue la importación PriorityLevel debajo de package com.mongodb.app, luego actualice las funciones addTask() para pasar taskPriority como parámetro y escriba el campo priority en 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
    }
3

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.

Dos artículos en una colección
haga clic para ampliar

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.

1

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 propiedad ownerId coincide 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í:

datos/SyncRepository.kt
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()
2

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.

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.

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.

Next

¿Qué son los servicios de aplicación Atlas?