Menu Docs
Página inicial do Docs
/ /
Serviços Atlas App

Tutorial: Atlas Device Sync para Kotlin

Nesta página

  • Objetivos de aprendizagem
  • Pré-requisitos
  • Comece com o Modelo
  • Configurar o Aplicativo Modelo
  • Abra o aplicativo
  • Explore a estrutura do aplicativo
  • Execute o aplicativo
  • Verificar o Backend
  • Modificar a Aplicação
  • Adicionar uma nova propriedade
  • Adicionar uma nova propriedade ao modelo
  • Definir a prioridade ao criar um novo item
  • Executar e testar
  • Alterar a assinatura
  • Atualizar a assinatura
  • Executar e testar
  • Conclusão
  • O que vem a seguir?

Tempo estimado para conclusão: 30 minutos, dependendo da sua experiência com o Kotlin

Realm fornece um Kotlin SDK que permite criar um aplicação móvel para Android com o Kotlin usando o Jetpack Compose. Este tutorial é baseado no modelo de aplicativo do Kotlin Flexible Sync , denominado kotlin.todo.flex, que ilustra a criação de um aplicação de gerenciamento de lista de tarefas. Este aplicação permite que o usuário:

  • Registre seu e-mail como uma nova conta de usuário.

  • Faça login na conta deles com o e-mail e a senha (e saia mais tarde).

  • Visualize, crie, modifique e exclua suas próprias tarefas.

  • Visualizar todas as tarefas, mesmo onde o usuário não é o proprietário.

O aplicativo de modelo também oferece uma alternância que simula o dispositivo no "Modo Offline". Essa alternância permite que você teste rapidamente a funcionalidade Device Sync no simulador, emulando o usuário que não tem conexão com a Internet. No entanto, você provavelmente removeria essa opção em um aplicativo de produção.

Este tutorial adiciona funcionalidades ao aplicativo modelo. Você adicionará um novo campo Priority ao modelo de Item existente e atualizará a inscrição do Flexible Sync para mostrar apenas itens dentro de uma faixa de priorities. Este exemplo ilustra como você pode adaptar o aplicativo modelo às suas próprias necessidades.

Este tutorial ilustra como você pode adaptar o aplicativo modelo para suas próprias necessidades.

Neste tutorial, você aprenderá como:

  • Atualize um modelo de objeto Realm com uma alteração não inovadora.

  • Atualizar uma inscrição do Device Sync.

  • Adicione um campo de query à configuração do Device Sync no servidor para alterar quais dados são sincronizados.

Observação

Confira o início rápido

Se você preferir começar com seu próprio aplicativo em vez de seguir um tutorial guiado, confira o Início rápido do Kotlin. Ele inclui exemplos de código copiáveis e as informações essenciais necessárias para configurar um backend do Atlas App Services.

  • Android Studio Bumblebee 2021.1.1 ou superior.

  • JDK 11 ou versão mais recente.

  • Plugin do Kotlin para Android Studio, versão 1.6.10 ou mais recente.

  • Um dispositivo virtual Android (AVD) usando uma arquitetura de CPU compatível.

  • Este tutorial começa com um aplicativo de modelo. Você precisa de uma conta do Atlas, de uma chave de API e do App Services CLI para criar um aplicativo modelo.

    • Você pode aprender mais sobre a criação de uma conta do Atlas na documentação de Comece a usar o Atlas. Para este tutorial, você precisa de uma conta do Atlas com um cluster de camada livre.

    • Você também precisa de uma chave de API do Atlas para a conta do MongoDB Cloud com a qual deseja se conectar. Você deve ser proprietário do projeto para criar um aplicativo modelo usando o App Services CLI.

    • Para saber mais sobre como instalar o App Services CLI, consulte Instalar o App Services CLI. Após instalar, execute o comando login utilizando a chave API para seu projeto Atlas.

Este tutorial baseia-se no aplicativo modelo do Kotlin SDK Flexible Sync denominado kotlin.todo.flex. Começamos com o aplicativo padrão e construímos novos recursos nele.

Para saber mais sobre os aplicativos de modelo, consulte Aplicativos de modelo.

Se você ainda não tiver uma conta do Atlas, cadastre-se para implantar um Aplicativo Modelo.

Siga o procedimento descrito no guia Como criar um aplicativo , e selecione Create App from Template. Selecione o modelo Real-time Sync . Isso cria um App Services App pré-configurado para uso com um dos clientes da aplicação do modelo do Device Sync .

Após criar um aplicativo de modelo, a interface do usuário exibe um modal rotulado Get the Front-end Code for your Template. Esse modal fornece instruções para baixar o código do cliente do aplicativo modelo como um arquivo .zip ou usar a CLI do App Services para obter o cliente.

Após selecionar o método CLI do .zip ou App Services, siga as instruções na tela para obter o código do cliente. Para este tutorial, selecione o código de cliente Kotlin (Android).

Observação

O utilitário ZIP padrão do Windows pode mostrar o arquivo .zip arquivo como vazio. Se você encontrar isso, use um dos programas de compactação de terceiros disponíveis.

O comando appservices apps create configura o back-end e cria um aplicativo modelo Kotlin para você usar como base para este tutorial.

Execute o seguinte comando em uma janela de terminal para criar um aplicativo chamado "MyTutorialApp" que é implantado na região US-VA com seu ambiente definido como "desenvolvimento" (em vez de produção ou controle de qualidade).

appservices app create \
--name MyTutorialApp \
--template kotlin.todo.flex \
--deployment-model global \
--environment development

O comando cria um novo diretório no caminho atual com o mesmo nome do valor do sinalizador --name.

Você pode bifurcar e clonar um repositório do Github que contenha o código do cliente Device Sync . O código do cliente Kotlin está disponível em https://github.com/mongodb/template-app-kotlin-todo.

Se você usar esse processo para obter o código do cliente, deverá criar um aplicativo modelo para usar com o cliente. Siga as instruções em Crie um aplicativo modelo para usar a interface do usuário do Atlas App Services, o App Services CLI ou o Admin API para criar um aplicativo modelo do Device Sync.

1

No Android Studio, abra a pasta kotlin.todo.flex .

Se você baixou o cliente como um arquivo .zip ou clonou o repositório GitHub do cliente, deverá inserir manualmente a ID do Aplicativo de Serviços de Aplicativo no local apropriado em seu cliente. Siga as instruções do Configuration no cliente README.md para saber onde inserir sua ID do aplicativo.

2

Reserve alguns minutos para explorar a organização do projeto enquanto o Android Studio indexa seu projeto. No diretório app/java/com.mongodb.app, você pode ver alguns arquivos dignos de nota:

arquivo
Propósito
ComposeItemActivity.kt
Classe de atividade que define o layout e fornece funcionalidade para abrir um domínio, gravar itens no domínio, desconectar um usuário e fechar um domínio.
ComposeLoginActivity.kt
Classe de atividade que define o layout e oferece funcionalidade para registrar e conectar um usuário.
TemplateApp.kt
Classe que inicializa o App Services App.

Neste tutorial, você estará trabalhando nos seguintes arquivos:

arquivo
Propósito
Item.kt
Localizado no diretório domain. Define o objeto de Realm que armazenamos no banco de dados.
AddItem.kt
Localizado no diretório ui/tasks. Contém a função composta que define o layout utilizado ao adicionar um item.
AddItemViewModel.kt
Localizado no diretório presentation/tasks. O modelo de exibição que contém lógica de negócios e gerencia o estado ao adicionar um item.
SyncRepository.kt
Localizado no diretório data. Repositório usado para acessar o Realm Sync e definir a inscrição Flexible Sync.
Strings.xml
Localizado no diretório res/values. Define os recursos de string de texto usados no aplicativo.
3

Sem fazer nenhuma alteração no código, você deve conseguir executar o aplicativo em um emulador Android usando o Android Studio ou em um dispositivo físico.

Execute a aplicação, registre uma nova conta de usuário e, em seguida, adicione um novo item à sua lista de tarefas.

4

Conecte-se ao Atlas App Services. Na aba Data Services, clique em Browse Collections. Na lista de bancos de dados, localize e expanda o banco de dados do todo e, em seguida, a coleção do Item. Você deverá ver o documento que criou nesta coleção.

1

Agora que você confirmou que tudo está funcionando conforme o esperado, podemos adicionar as alterações. Neste tutorial, decidimos que queremos adicionar uma propriedade "priority" a cada item para que possamos filtrar os itens por seu nível de prioridade. A propriedade priority será mapeada a um enum PriorityLevel para restringir os valores possíveis, e usaremos o ordinal de cada enum para corresponder ao número inteiro priority para que possamos consultar com base em um nível de priority numérico posteriormente.

Para fazer isso, siga estas etapas:

  1. Dentro da pasta app/java/com.mongodb.app/domain, abra o arquivo de classe Item.

  2. Adicione um enum PriorityLevel para restringir os valores possíveis. Adicione também uma propriedade priority à classe Item, que define a priority padrão como 3, indicando que é um item de tarefas de baixa priority:

    domain/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. Na pasta ui/tasks, abra o arquivo AddItem.kt. Esse arquivo define as funções que podem ser compostas para a interface do usuário que são exibidas quando um usuário clica no botão '+' para adicionar um novo item de tarefa.

  2. Primeiro, adicione as seguintes importações abaixo do package com.mongodb.app:

    IU/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. Agora podemos adicionar um campo suspenso à função composta do AddItemPrompt que permitirá ao usuário escolher um nível de prioridade em uma lista usando os enums PriorityLevel como valores disponíveis:

    IU/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
    )
    }

    O Android Studio identificará vários erros. Corrigiremos isso nas próximas etapas adicionando as funções relacionadas.

  4. Em seguida, definiremos a etiqueta do campo suspenso como um recurso de string. Abra o arquivo res/values/strings.xml e adicione o seguinte antes de fechar o elemento "recurso":

    res/values/strings.xml
    <string name="item_priority">Item Priority</string>
  5. Agora, na pasta presentation/tasks, abra o arquivo AddItemViewModel.kt . Aqui adicionaremos a lógica de negócios relacionada ao nosso novo campo suspenso.

    Adicione a importação PriorityLevel abaixo do package com.mongodb.app e, em seguida, adicione as variáveis e funções à classe AddItemViewModel necessária para lidar com as alterações de estado no menu suspenso:

    presentation/tasks/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
    }

    Agora, atualize as funções addTask() e cleanUpAndClose() para incluir o novo parâmetro taskPriority, atualize a mensagem com as informações de prioridade e redefina o campo de priority para baixo assim que a exibição Adicionar item for fechado:

    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, na pasta data, abra o arquivo SyncRepository.kt para refletir as mesmas alterações na função addTask(), que grava o item no realm.

    Primeiro, adicione a importação PriorityLevel abaixo do package com.mongodb.app e, em seguida, atualize as funções addTask() para passar o taskPriority como parâmetro e gravar o campo priority no domínio como um número inteiro (usando o enum ordinal):

    data/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

Agora você pode executar o aplicativo novamente. Faça login usando a conta que você criou neste tutorial. Você verá o único item que criou anteriormente. Adicione um novo item e você verá que agora pode definir a priority. Escolha High para a priority e salve o item.

Agora volte para a página de dados do Atlas em seu navegador e atualize a coleção do Item. Você deve agora visualizar o novo Item com o campo priority adicionado e configurado para 1. O item existente não tem um campo priority.

Dois itens em uma coleção
clique para ampliar

Observação

Por que isso não interrompeu a sincronização?

A adição de uma propriedade a um objeto Realm não é uma alteração de ruptura e, portanto, não exige uma reinicialização do cliente. O aplicativo de modelo tem o Modo de Desenvolvimento habilitado, portanto, as alterações no objeto Realm do cliente são refletidas no esquema do lado do servidor. Para obter mais informações, consulte Modo de desenvolvimento e Atualizar seu modelo de dados.

1

Na pasta app/java/com.mongodb.app/data, abra o arquivo SyncRepository.kt, onde definimos a inscrição Flexible Sync. A inscrição define quais documentos sincronizamos com o dispositivo e a conta do usuário. Encontre a função getQuery(). Você pode ver que atualmente estamos assinando duas inscrições:

  • MINE: Todos os documentos onde a propriedade ownerId corresponde ao usuário autenticado.

  • ALL: Todos os documentos de todos os usuários.

Queremos atualizar a inscrição MINE para somente itens de sincronização marcados como prioridade Alta ou Grave.

Como você deve se lembrar, o campo priority é do tipo int, em que a prioridade mais alta ("Severe") tem o valor 0 e a prioridade mais baixa ("Low") tem o valor 3. Podemos fazer comparações diretas entre um número inteiro e a propriedade de prioridade. Para isso, edite a instrução RQL para incluir documentos em que a prioridade seja igual ou inferior a PriorityLevel.High (ou 1), conforme mostrado aqui:

data/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()
}

Também forçaremos a query de inscrição a recalcular quais documentos sincronizar toda vez que abrirmos o aplicativo.

Para tal, localize a função SyncConfiguration.Builder().initialSubscriptions() que nosso aplicativo chama no início. Primeiro, adicione o parâmetro reRunOnOpen definido para true e, em seguida, defina updateExisting para true, o que permite que a query existente seja atualizada.

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

Execute o aplicativo novamente. Faça login usando a conta que você criou neste tutorial.

Após um momento inicial em que o Realm ressincroniza a collection de documentos, você verá o novo item de alta priority que você criou.

Dica

Alterando assinaturas com o modo de desenvolvedor ativado

Neste tutorial, quando você altera a assinatura e a query no campo de prioridade pela primeira vez, o campo é adicionado automaticamente ao Device Sync Collection Queryable Fields. Isso ocorre porque o aplicativo de modelo tem o Modo de Desenvolvimento habilitado por padrão. Se o Modo de Desenvolvimento não estivesse habilitado, você teria que adicionar manualmente o campo como um campo consultável para usá-lo em uma query de sincronização do lado do cliente.

Para obter mais informações, consulte Campos consultáveis.

Se quiser testar ainda mais a funcionalidade, crie itens de várias prioridades. Você verá que, se tentar adicionar um item com prioridade menor que Alta, receberá uma mensagem do sistema indicando que não tem permissão. E, se você verificar seus logs usando o Logcat, verá uma mensagem indicando que o item foi "adicionado com sucesso", seguida por um erro de sincronização:

ERROR "Client attempted a write that is outside of permissions or query
filters; it has been reverted"

Isso porque, nesse cenário, o Realm cria o item localmente, sincroniza-o com o backend e, em seguida, reverte a escrita porque ele não atende às regras de assinatura.

Você também notará que o documento que você criou inicialmente não está sincronizado, pois tem uma priority de null. Se você quiser que este Item seja sincronizado, você pode editar o documento na interface do usuário do Atlas e adicionar um valor para o campo prioritário.

Adicionar uma propriedade a um objeto de Realm existente não é uma alteração significativa, e o modo de desenvolvimento garante que a alteração de esquema se reflita no lado do servidor.

Observação

Compartilhar feedback

Como foi? Use o widget Rate this page no canto inferior direito da página para avaliar sua eficácia. Ou registre um problema no repositório GitHub se você teve algum problema.

Próximo

O que são os Serviços de Aplicativo Atlas?