Join us Sept 17 at .local NYC! Use code WEB50 to save 50% on tickets. Learn more >
MongoDB Event
Menu Docs
Página inicial do Docs
/ /
Serviços Atlas App
/ /

Tutorial: Migrar um aplicativo React Native para o PowerSync

A partir de 2024 de setembro, o Atlas Device SDKs (Realm), Device Sync e App Services foram descontinuados. Isso significa que os usuários desses serviços terão que migrar para outra solução até 2025 de setembro. Se precisar de mais tempo, entre em contato com o suporte.

O PowerSync é uma das principais alternativas ao Atlas Device Sync. É uma solução baseada em SQLite e pode ser a solução certa para migrar se você tiver um aplicação móvel usando o Device Sync.

Este tutorial guiará você pelas etapas necessárias para migrar um aplicação móvel do Device Sync , escrito no React Native, para o PowerSync. Seus dados de backend permanecerão no Atlas, então você precisará configurar o serviço PowerSync, atualizar os esquemas e vinculações do banco de dados local e configurar um serviço de backend para gravar no Atlas.

This tutorial uses a Realm todo list application for React Native available in the realm2powersync example repository.

Primeiro, você precisa implantar um Atlas Cluster e inserir alguns dados de teste. Isto irá orientá-lo como se estivesse configurando o Atlas pela primeira vez. Se você já tiver um cluster implantado, fique à vontade para pular.

  1. Navegue até MongoDB Atlas e registre-se para uma conta Atlas , ou entre se você já tiver uma conta.

  2. Em seguida, crie um cluster.

    Captura de tela da UI

    Para fins de teste, selecione o cluster M0 (grátis) com as configurações padrão. Sinta-se à vontade para fazer quaisquer alterações adicionais para atender às suas necessidades.

  3. Clique em Criar sistema.

    Captura de tela da UI

    Você retorna ao seu painel. O modal Conectar ao cluster é exibido automaticamente.

  4. Clique em Escolher um método de conexão e selecione Drivers.

    Captura de tela da UI

    Nesta tela, copie a URL que exibe na etapa 3.

    Captura de tela da UI

    Adicione a string de conexão ao código do seu aplicação . Esta é a sua string de conexão; é necessário acessar sua instância MongoDB . Salve a string de conexão para referência futura.

    Você criará um nome de usuário e uma senha nas próximas etapas que a instância do PowerSync usará para se conectar ao banco de dados.

  5. Clique em Concluído para fechar o modal.

    Após a conclusão da implantação do cluster, seu dashboard deverá ter a seguinte aparência.

  6. Clique em Adicionar dados para criar um novo banco de dados.

    Captura de tela da UI

    No cartão Criar um Banco de Dados no Atlas , clique em INICIAR.

    Captura de tela da UI

    Crie um banco de dados chamado PowerSync e uma collection chamada Item e clique em Criar banco de dados.

    Captura de tela da UI

    Você retorna ao painel e deve ver o banco de dados e a coleção recém-criados:

    Captura de tela da UI

    Finalmente, você precisa criar um novo usuário que o PowerSync usará para se conectar a esse banco de dados.

    Na barra lateral esquerda, clique em Acesso ao banco de dados no cabeçalho Segurança.

    Captura de tela da UI

    Clique em Add New Database User, crie um novo usuário chamado powersync e forneça uma senha. Observe o nome de usuário e a senha a serem usados na string de conexão que você copiou anteriormente.

    Observação

    If your username or password contains any of the following special characters, you must convert them to a URL-safe format for your connection string: $, :, /, ?, !, #, [, ], @. You can do this manually or use a URL-encoding application, such as urlencoder.org.

    Na seção Privilégios do usuário do banco de dados, clique em Adicionar privilégio específico e adicione um privilégio para readWrite dbAdmin um role e um para o banco de dados PowerSync.

    Captura de tela da UI

    Clique em Adicionar Usuário.

    Você deve ver o usuário recém-criado com as permissões de banco de dados necessárias.

    Captura de tela da UI

For more details on user permissions, refer to the MongoDB section of the PowerSync Source Database Setup guide.

In order for PowerSync to access the database running in Atlas, you must add the service IP addresses to the IP access list. These IP addresses are listed in the PowerSync Security & IP Filtering documentation.

Na barra lateral esquerda, clique em Acesso à rede no cabeçalho Segurança.

Clique em + Adicionar Endereço IP e insira o endereço IP. Para melhor ajudar qualquer pessoa a administrar essa lista no futuro, também recomendamos inserir PowerSync como o comentário opcional.

Clique em Confirmar e repita para cada IP.

Atualize os espaços reservados na string de conexão que você copiou anteriormente com o nome de usuário e a senha do usuário de banco de dados.

Nesta etapa, você importará alguns dados de amostra que serão utilizados para sincronizar dados em etapas futuras.

Primeiro, instale o MongoDB Database Tools para obter acesso ao mongoimport. Consulte as instruções do Guia de instalação do seu sistema operacional.

Depois de instalar o database-tools, digite o seguinte no terminal para confirmar que você pode acessar o mongoimport:

mongoimport --version

Isso deve retornar a versão da ferramenta. Consulte o Guia de instalação acima se estiver tendo problemas.

Em seguida, crie um arquivo JSON denominado sample.json com o seguinte conteúdo:

[
{
"isComplete": false,
"summary": "Complete project documentation",
"owner_id": "mockUserId"
},
{
"isComplete": true,
"summary": "Buy groceries",
"owner_id": "mockUserId"
},
{
"isComplete": false,
"summary": "Schedule dentist appointment",
"owner_id": "mockUserId"
},
{
"isComplete": false,
"summary": "Prepare presentation for next week",
"owner_id": "mockUserId"
},
{
"isComplete": true,
"summary": "Pay utility bills",
"owner_id": "mockUserId"
},
{
"isComplete": false,
"summary": "Fix bug in login system",
"owner_id": "mockUserId2"
},
{
"isComplete": false,
"summary": "Call mom",
"owner_id": "mockUserId"
},
{
"isComplete": true,
"summary": "Submit expense reports",
"owner_id": "mockUserId2"
},
{
"isComplete": false,
"summary": "Plan team building event",
"owner_id": "mockUserId2"
},
{
"isComplete": false,
"summary": "Review pull requests",
"owner_id": "mockUserId2"
}
]

Estes dados de amostra contêm alguns itens da lista de tarefas. O owner_id será usado para exemplos de filtragem mais tarde neste tutorial.

Para importar esse JSON, digite o seguinte comando, substituindo o espaço reservado <connection-string> por sua string de conexão:

mongoimport --uri="<connection-string>" --db=PowerSync --collection=Item
--file=sample.json --jsonArray

Você deve ver a seguinte mensagem:

10 document(s) imported successfully. 0 document(s) failed to import.

Caso contrário, confirme se seus parâmetros de comando (incluindo string de conexão) estão corretos e se seu usuário do Atlas tem o acesso correto ao banco de dados .

Você pode visualizar e gerenciar os documentos inseridos navegando até sua coleção na UI do Atlas ou usando o aplicação de desktop visual MongoDB Compass . Para exibir e gerenciar seu banco de dados e collections por meio do MongoDB Compass, você deve se conectar usando a mesma string de conexão.

Captura de tela da UI

Now navigate to PowerSync and register or sign in.

Se você estiver fazendo login pela primeira vez, precisará criar uma nova instância para começar.

Crie uma nova instância denominada TodoList.

Captura de tela da UI

Selecione MongoDB como banco de dados de conexão .

Captura de tela da UI

Utilize sua string de conexão do Atlas para preencher as configurações de conexão.

Importante

Use uma versão reduzida da string de conexão que não contenha seu nome de usuário, senha ou outros parâmetros de URL . Por exemplo, sua conexão será parecida com mongodb+srv://m0cluster.h6folge.mongodb.net/.

Insira o nome do banco de dados ("PowerSync"), o nome de usuário ("Powersync") e a senha que você atribuiu a esta conta em uma etapa anterior.

Captura de tela da UI

Clique em Testar conexão para garantir que você possa se conectar com sucesso.

Se você vir o erro a seguir, confirme se todos os IPs de serviço PowerSync necessários estão em sua lista de acesso IP do Atlas .

Captura de tela da UI

If you are still having issues, refer to the PowerSync Database Connection Guide for MongoDB connections.

Clique em Avançar para implementar sua nova instância do PowerSync. Isso pode levar alguns minutos para ser concluído.

Depois que sua instância for implantada, você pode garantir que pode visualizar os dados migrados criando algumas regras básicas de sincronização.

Primeiro, remova as regras de sincronização padrão e substitua-as pelo seguinte:

bucket_definitions:
user_buckets:
parameters: SELECT request.user_id() as user_id
data:
- SELECT _id as id, * FROM "Item" WHERE bucket.user_id = 'global'
OR owner_id = bucket.user_id

Para que os itens sejam sincronizados corretamente com o serviço PowerSync, observe o seguinte:

  • O _id deve ser mapeado para id.

  • O nome da collection "Item" deve ser colocado entre aspas. Isso ocorre porque o nome da nossa coleção começa com uma letra maiúscula.

  • Os blocos específicos do usuário devem corresponder a um user_id de global, que fornece acesso a todo o banco de dados. Caso contrário, você corresponderá ao user_id fornecido, que será recuperado do token de autenticação.

Note that PowerSync Sync Rules are a pretty deep topic. To learn more, you can check out this Sync Rules blog post or the PowerSync Sync Rules documentation.

Clique em Salvar e implantar. Mais uma vez, pode levar alguns minutos para que o sistema seja concluído.

Após a conclusão do sistema, você verá o seguinte:

Captura de tela da UI

Após a conclusão do sistema, você deverá ver o status apropriado.

If you get any errors, ensure that the PowerSync user is set up with the permissions listed in the PowerSync Source Database Setup documentation.

Clique em Gerenciar instâncias para revisar as regras de sincronização e o status do sistema.

Para finalizar esta configuração, você usará o Aplicativo de Diagnóstico PowerSync para visualizar os itens da lista de tarefas que acabou de criar e adicionar às suas regras de sincronização. Para usar essa ferramenta, primeiro você precisa criar um token de desenvolvimento.

  • Na parte superior da página PowerSync, clique em Manage Instances.

  • Na barra lateral esquerda, clique nas reticências (...) ao lado de TodoList para abrir o menu de contexto dessa instância e, em seguida, selecione Editar instância.

  • Selecione a aba Autenticação do cliente e clique em Habilitar tokens de desenvolvimento.

  • Clique em Salvar e implantar.

Captura de tela da UI

Clique nas reticências (...) ao lado de TodoList para abrir o menu de contexto dessa instância novamente e selecione Gerar token de desenvolvimento.

Você será solicitado a fornecer um assunto de token/user_id. Isso agirá como user_id e você pode configurar suas regras de sincronização para agir de acordo com ele.

Com as regras de sincronização que definimos anteriormente, você pode definir o Subject/User_id como global para gerar um token que terá acesso a todo o conjunto de dados. Você também pode definir isso como mockUserId ou mockUserId2 para sincronizar em um owner_idespecífico.

Copy the generated token, then open the Diagnostics App and paste in the Development Token.

Observação

O token de desenvolvimento expira em 12 horas. A ferramenta de diagnóstico interromperá a sincronização com o Atlas após a expiração, portanto, você deve gerar um novo token se quiser que ela retome a sincronização.

Você deve ver uma página semelhante a esta.

Captura de tela da UI

Na barra lateral esquerda, clique em Console SQL.

Crie uma query SELECT para visualizar todos os itens:

SELECT * FROM Item
Captura de tela da UI

Agora você tem todos os serviços necessários para sincronizar seu banco de dados MongoDB com um aplicação móvel.

In this phase, you are going to clone a Realm todo list application for React Native. The main branch of the example repository contains the final result of the migration.

Para acompanhar este guia usando o repositório de exemplo , faça o checkout da ramificação 00-Start-Here:

git clone https://github.com/takameyer/realm2powersync
cd realm2powersync
git checkout 00-Start-Here

Em seguida, instale as dependências para que o editor possa selecionar quaisquer importações e garantir que não haja erros ao editar este projeto.

Importante

This tutorial assumes you have the latest version of Node.js installed.

npm install

Como o aplicação pressupõe que há um Atlas cluster com um serviço Device Sync ativo, ele ainda não pode ser executado. Nas próximas etapas, você fará as modificações necessárias para executar o projeto como um aplicação somente local.

Você deve remover as partes do Atlas Device Sync para que o aplicação seja executado com dados somente locais.

Primeiro, abra o source/AppWrapper.txs e remova a configuração do AppProvider, UserProvider e sync.

O arquivo AppWrapper.txs atualizado deve ser semelhante ao seguinte:

import React from 'react';
import { StyleSheet, View, ActivityIndicator } from 'react-native';
import { RealmProvider } from '@realm/react';
import { App } from './App';
import { Item } from './ItemSchema';
const LoadingIndicator = () => {
return (
<View style={styles.activityContainer}>
<ActivityIndicator size="large" />
</View>
);
};
export const AppWrapper = () => {
return (
<RealmProvider schema={[Item]} fallback={LoadingIndicator}>
<App />
</RealmProvider>
);
};
const styles = StyleSheet.create({
activityContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
padding: 10,
},
});

Em seguida, abra source/App.tsx e remova as partes sobre dataExplorerLink e os botões de cabeçalho para OfflineMode e Logout (isso será implementado posteriormente).

O arquivo App.tsx atualizado deve ser semelhante ao seguinte:

import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { LogoutButton } from './LogoutButton';
import { ItemListView } from './ItemListView';
import { OfflineModeButton } from './OfflineModeButton';
const Stack = createStackNavigator();
const headerRight = () => {
return <OfflineModeButton />;
};
const headerLeft = () => {
return <LogoutButton />;
};
export const App = () => {
return (
<>
{/* All screens nested in RealmProvider have access
to the configured realm's hooks. */}
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Your To-Do List"
component={ItemListView}
options={{
headerTitleAlign: 'center',
//headerLeft,
//headerRight,
}}
/>
</Stack.Navigator>
</NavigationContainer>
<View style={styles.footer}>
<Text style={styles.footerText}>
Log in with the same account on another device or simulator to see
your list sync in real time.
</Text>
</View>
</SafeAreaProvider>
</>
);
};
const styles = StyleSheet.create({
footerText: {
fontSize: 12,
textAlign: 'center',
marginVertical: 4,
},
hyperlink: {
color: 'blue',
},
footer: {
paddingHorizontal: 24,
paddingVertical: 12,
},
});

Por fim, abra o source/ItemListView.tsx e faça as seguintes atualizações:

  • Remova o código de assinatura Flexible Sync

  • Substituir usuário por um usuário simulado: - const user={ id: 'mockUserId' };

  • Remova quaisquer referências dataExplorerer

  • Remover funcionalidade da chave Show All Tasks (isso será implementado posteriormente)

O arquivo ItemListView.tsx atualizado deve ser semelhante ao seguinte:

import React, { useCallback, useState, useEffect } from 'react';
import { BSON } from 'realm';
import { useRealm, useQuery } from '@realm/react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Alert, FlatList, StyleSheet, Switch, Text, View } from 'react-native';
import { Button, Overlay, ListItem } from '@rneui/base';
import { CreateToDoPrompt } from './CreateToDoPrompt';
import { Item } from './ItemSchema';
import { colors } from './Colors';
export function ItemListView() {
const realm = useRealm();
const items = useQuery(Item).sorted('_id');
const user = { id: 'mockUserId' };
const [showNewItemOverlay, setShowNewItemOverlay] = useState(false);
const [showAllItems, setShowAllItems] = useState(true);
// createItem() takes in a summary and then creates an Item object with that summary
const createItem = useCallback(
({ summary }: { summary: string }) => {
// if the realm exists, create an Item
realm.write(() => {
return new Item(realm, {
summary,
owner_id: user?.id,
});
});
},
[realm, user],
);
// deleteItem() deletes an Item with a particular _id
const deleteItem = useCallback(
(id: BSON.ObjectId) => {
// if the realm exists, get the Item with a particular _id and delete it
const item = realm.objectForPrimaryKey(Item, id); // search for a realm object with a primary key that is an objectId
if (item) {
if (item.owner_id !== user?.id) {
Alert.alert("You can't delete someone else's task!");
} else {
realm.write(() => {
realm.delete(item);
});
}
}
},
[realm, user],
);
// toggleItemIsComplete() updates an Item with a particular _id to be 'completed'
const toggleItemIsComplete = useCallback(
(id: BSON.ObjectId) => {
// if the realm exists, get the Item with a particular _id and update it's 'isCompleted' field
const item = realm.objectForPrimaryKey(Item, id); // search for a realm object with a primary key that is an objectId
if (item) {
if (item.owner_id !== user?.id) {
Alert.alert("You can't modify someone else's task!");
} else {
realm.write(() => {
item.isComplete = !item.isComplete;
});
}
}
},
[realm, user],
);
return (
<SafeAreaProvider>
<View style={styles.viewWrapper}>
<View style={styles.toggleRow}>
<Text style={styles.toggleText}>Show All Tasks</Text>
<Switch
trackColor={{ true: '#00ED64' }}
onValueChange={() => {
setShowAllItems(!showAllItems);
}}
value={showAllItems}
/>
</View>
<Overlay
isVisible={showNewItemOverlay}
overlayStyle={styles.overlay}
onBackdropPress={() => setShowNewItemOverlay(false)}>
<CreateToDoPrompt
onSubmit={({ summary }) => {
setShowNewItemOverlay(false);
createItem({ summary });
}}
/>
</Overlay>
<FlatList
keyExtractor={item => item._id.toString()}
data={items}
renderItem={({ item }) => (
<ListItem key={`${item._id}`} bottomDivider topDivider>
<ListItem.Title style={styles.itemTitle}>
{item.summary}
</ListItem.Title>
<ListItem.Subtitle style={styles.itemSubtitle}>
<Text>{item.owner_id === user?.id ? '(mine)' : ''}</Text>
</ListItem.Subtitle>
<ListItem.Content>
{!item.isComplete && (
<Button
title="Mark done"
type="clear"
onPress={() => toggleItemIsComplete(item._id)}
/>
)}
<Button
title="Delete"
type="clear"
onPress={() => deleteItem(item._id)}
/>
</ListItem.Content>
</ListItem>
)}
/>
<Button
title="Add To-Do"
buttonStyle={styles.addToDoButton}
onPress={() => setShowNewItemOverlay(true)}
/>
</View>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
viewWrapper: {
flex: 1,
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
addToDoButton: {
backgroundColor: colors.primary,
borderRadius: 4,
margin: 5,
},
completeButton: {
backgroundColor: colors.primary,
borderRadius: 4,
margin: 5,
},
showCompletedButton: {
borderRadius: 4,
margin: 5,
},
showCompletedIcon: {
marginRight: 5,
},
itemTitle: {
flex: 1,
},
itemSubtitle: {
color: '#979797',
flex: 1,
},
toggleRow: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
},
toggleText: {
flex: 1,
fontSize: 16,
},
overlay: {
backgroundColor: 'white',
},
status: {
width: 40,
height: 40,
justifyContent: 'center',
borderRadius: 5,
borderWidth: 1,
borderColor: '#d3d3d3',
backgroundColor: '#ffffff',
alignSelf: 'flex-end',
},
delete: {
alignSelf: 'flex-end',
width: 65,
marginHorizontal: 12,
},
statusCompleted: {
borderColor: colors.purple,
},
statusIcon: {
textAlign: 'center',
fontSize: 17,
color: colors.purple,
},
});

Com essas alterações, o aplicativo deve funcionar em um banco de dados local.

Antes de iniciar a migração, você precisa construir e executar o aplicação atualizado para verificar se ele funciona conforme o esperado.

Para iOS, execute os seguintes comandos:

npx pod-install
npm run ios

Para Android, execute o seguinte comando:

npm run android

Note that any build errors are out of the scope of this documentation. If you are experiencing build-related issues, consult the React Native documentation to ensure that your environment is set up correctly.

Enquanto seu aplicativo está em execução, você pode verificar a funcionalidade básica. Você deve ser capaz de:

  • Criar novos itens

  • Marcar itens como concluídos

  • Excluir itens

Captura de tela da UI

Agora que você tem um aplicação Realm somente local em execução, pode começar a converter esse aplicação para usar uma versão somente local do cliente PowerSync.

O PowerSync usa um banco de dados baseado em SQLite, portanto, será necessário fazer algumas modificações no esquema para garantir que seja compatível.

To accomplish this, you’ll need to set up the PowerSync client. For detailed instructions, you can refer to the @powersync/react-native npm repository or the PowerSync React Native Setup documentation.

Primeiro, execute o seguinte comando para adicionar dependências para o PowerSync React Native Client, o banco de dados SQLite de apoio, um iterador assíncrono polyfill (necessário conforme as instruções), bem como o bson dependência (usada para gerar ObjectId's para inserir documentos no MongoDB):

npm install @powersync/react-native @journeyapps/react-native-quick-sqlite @azure/core-asynciterator-polyfill bson

Para configurar o polyfill, abra o index.js e adicione import '@azure/core-asynciterator-polyfill'; ao topo do arquivo.

O arquivo index.js atualizado deve ser semelhante ao seguinte:

import '@azure/core-asynciterator-polyfill';
import 'react-native-get-random-values';
import {AppRegistry} from 'react-native';
import {AppWrapper} from './source/AppWrapper';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => AppWrapper);

Agora que as dependências foram adicionadas, você precisa reconstruir o aplicação:

  • Para iOS, execute pod-install.

  • Para Android, atualize o SDK mínimo exigido para 24 para ser compatível com react-native-quick-sqlite. Para fazer isso, abra android/build.gradle e altere minSdkVersion de 21 para 24.

Agora você configurará os tipos de dados e esquemas para o banco de dados local.

Refer to the PowerSync MongoDB Type Mapping documentation to determine how to set up your specific schema. The following is a quick reference of the available types:

Tipo
Descrição

zero

valores indefinidos ou valores não definidos

inteiro

um número inteiro assinado de 64bits

real

um número de ponto flutuante de 64bits

text

uma string de texto UTF-8

blob

Dados binários

Para este tutorial, você modificará source/ItemSchema.tsx da seguinte forma:

import {column, Schema, Table} from '@powersync/react-native';
export const ItemSchema = new Table({
isComplete: column.integer,
summary: column.text,
owner_id: column.text,
});
export const AppSchema = new Schema({
Item: ItemSchema,
});
export type Database = (typeof AppSchema)['types'];
export type Item = Database['Item'];

Importante

O nome da propriedade passado para Schema representa o nome da tabela local e da collection MongoDB . Neste caso, certifique-se de que ele seja nomeado Item.

Observe que este código exporta os tipos diretamente do AppSchema, em vez de defini-los manualmente.

To get access to PowerSync and bind your data, you will need access to the hooks and providers for the PowerSync client. This functionality is provided through the PowerSyncContext component.

Primeiro, atualize o source/AppWrapper.tsx para usar o PowerSyncContext e inicialize seu cliente PowerSync:

import React from 'react';
import {App} from './App';
import {AppSchema} from './ItemSchema';
import {PowerSyncContext, PowerSyncDatabase} from '@powersync/react-native';
const powerSync = new PowerSyncDatabase({
schema: AppSchema,
database: {
dbFilename: 'powersync.db',
},
});
powerSync.init();
export const AppWrapper = () => {
return (
<PowerSyncContext.Provider value={powerSync}>
<App />
</PowerSyncContext.Provider>
);
};

Em seguida, atualize ItemListView.tsx para usar o cliente PowerSync. Para conseguir isso, você deve atualizar os ganchos usados na parte superior deste componente:

  • Para obter acesso ao banco de dados local para fazer gravações e atualizações, use o hook usePowerSync.

  • Para obter uma lista de itens da lista de tarefas que são renderizados automaticamente na atualização, use o hook useQuery.

Faça as seguintes alterações:

  • Remover import { BSON } from 'realm';

  • Adicionar import { ObjectId } from 'bson';

  • Altere as duas primeiras linhas da função ItemListView para corresponder ao seguinte:

    export function ItemListView() {
    const db = usePowerSync();
    const {data: items} = useQuery<Item>('SELECT * FROM Item');

Em seguida, você precisa atualizar os métodos createItem, deleteItem e toggleItemIsComplete.

Para cada um desses métodos, você usará o objeto db retornado de usePowerSync. Assim como no Realm, o banco de dados local abre uma transação para executar qualquer operação mutável, como inserir, atualizar ou excluir. Você também adicionará blocos try/catch para propagar quaisquer erros no frontend do aplicação.

Observe que o código está importando ObjectId de bson para criar os IDs exclusivos para cada item. Lembre-se de que o PowerSync espera que os itens da chave primária sejam denominados id.

O código de criação também implementa os valores padrão para os itens diretamente nessa lógica. Nesse caso, isComplete é inicializado como falso e o id é inicializado com o resultado de string do ObjectId recém-criado.

O método createItem pode ser implementado da seguinte forma:

// createItem() takes in a summary and then creates an Item object with that summary
const createItem = useCallback(
async ({summary}: {summary: string}) => {
try {
// start a write transaction to insert the new Item
db.writeTransaction(async tx => {
await tx.execute(
'INSERT INTO Item (id, summary, owner_id, isComplete) VALUES (?, ?, ?, ?)',
[new ObjectId().toHexString(), summary, user?.id, false],
);
});
} catch (ex: any) {
Alert.alert('Error', ex?.message);
}
},
[db],
);

Os métodos deleteItem e toggleItemIsComplete são semelhantes, portanto, implemente-os da seguinte maneira:

// deleteItem() deletes an Item with a particular _id
const deleteItem = useCallback(
async (id: String) => {
// start a write transaction to delete the Item
try {
db.writeTransaction(async tx => {
await tx.execute('DELETE FROM Item WHERE id = ?', [id]);
});
} catch (ex: any) {
Alert.alert('Error', ex?.message);
}
},
[db],
);
// toggleItemIsComplete() updates an Item with a particular _id to be 'completed'
const toggleItemIsComplete = useCallback(
async (id: String) => {
// start a write transaction to update the Item
try {
db.writeTransaction(async tx => {
await tx.execute(
'UPDATE Item SET isComplete = NOT isComplete WHERE id = ?',
[id],
);
});
} catch (ex: any) {
Alert.alert('Error', ex?.message);
}
},
[db],
);

Finalmente, atualize o FlatList renderizado. Você irá:

  • Substituir instâncias de _id por id

  • Atualize o keyExtractor do FlatList para usar a string id diretamente.

  • Anteriormente, o banco de dados retornava um ObjectId. Isso precisará ser convertido em uma string.

O FlatList atualizado agora se parece com o seguinte:

<FlatList
keyExtractor={item => item.id}
data={items}
renderItem={({item}) => (
<ListItem key={`${item.id}`} bottomDivider topDivider>
<ListItem.Title style={styles.itemTitle}>
{item.summary}
</ListItem.Title>
<ListItem.Subtitle style={styles.itemSubtitle}>
<Text>{item.owner_id === user?.id ? '(mine)' : ''}</Text>
</ListItem.Subtitle>
<ListItem.Content>
<Pressable
accessibilityLabel={`Mark task as ${
item.isComplete ? 'not done' : 'done'
}`}
onPress={() => toggleItemIsComplete(item.id)}
style={[
styles.status,
item.isComplete && styles.statusCompleted,
]}>
<Text style={styles.statusIcon}>
{item.isComplete ? '✓' : '○'}
</Text>
</Pressable>
</ListItem.Content>
<ListItem.Content>
<Pressable
accessibilityLabel={'Remove Item'}
onPress={() => deleteItem(item.id)}
style={styles.delete}>
<Text style={[styles.statusIcon, {color: 'blue'}]}>
DELETE
</Text>
</Pressable>
</ListItem.Content>
</ListItem>
)}
/>

Depois de atualizar o código, você poderá usar um cliente PowerSync local.

Para verificar, reconstrua o aplicação. Se você estiver usando o iOS, não se lembre de atualizar os Pods com npx pod-install.

Captura de tela da UI

Agora você deve ter um aplicação funcional que permita adicionar, atualizar e excluir itens da lista de tarefas usando o PowerSync.

If you encounter issues, you can view the changes made up to this point in the 02-Migrate-Local-Client branch of the example repository.

Seu aplicação móvel agora está pronto para sincronizar dados em tempo real do MongoDB.

Observação

Você provavelmente notou que os dados do Realm ainda não foram migrados. Este guia pressupõe que o cluster MongoDB hospedado no Atlas é a fonte da verdade para os dados e sincronize isso com o aplicação. A migração de dados locais está fora do escopo deste tutorial, mas pode ser abordada em documentação futura.

Agora você deve ter um serviço PowerSync em execução que contenha dados sincronizados do Atlas, que foram verificados usando a ferramenta de diagnóstico PowerSync.

Nesta fase, você obterá esses dados para sincronizar com o aplicação React Native .

Para começar, você precisa criar uma maneira de definir algumas variáveis de ambiente para tokens e endpoints.

Primeiro, instale react-native-dotenv em suas dependências de desenvolvimento. Este é um plugin-in babel que pega um arquivo .env da raiz do projeto e permite importar variáveis de ambiente diretamente para o aplicação.

npm install -D react-native-dotenv

Em seguida, adicione a seguinte linha ao arquivo babel.config.js:

module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['module:react-native-dotenv'],
};

Crie um novo diretório denominado types e, nele, crie um novo arquivo denominado env.d.ts que contenha as seguintes variáveis que queremos importar:

declare module '@env' {
export const AUTH_TOKEN: string;
export const POWERSYNC_ENDPOINT: string;
}

Você precisa recuperar os valores necessários para as variáveis de ambiente do PowerSync.

  • No console do PowerSync, na barra lateral esquerda, clique em ... ao lado de TodoList para abrir o menu de contexto.

  • Selecione Editar instância.

  • Copie e salve o URL.

Captura de tela da UI

Em seguida, gere um novo token de desenvolvimento para sua instância com o assunto/user_id. mockUserId Copie e salve o token gerado.

No projeto do aplicação , crie um arquivo .env no diretório raiz e cole o endpoint e o token do PowerSync que você acabou de gerar:

POWERSYNC_ENDPOINT=<endpoint>
AUTH_TOKEN=<dev-token>

Você precisará refatorar um pouco seu aplicação para que ele possa se conectar à sua instância do PowerSync.

Primeiro, crie um novo arquivo em source chamado PowerSync.ts e cole o seguinte:

import { AppSchema } from './ItemSchema';
import {
AbstractPowerSyncDatabase,
PowerSyncDatabase,
} from '@powersync/react-native';
import { AUTH_TOKEN, POWERSYNC_ENDPOINT } from '@env';
const powerSync = new PowerSyncDatabase({
schema: AppSchema,
database: {
dbFilename: 'powersync.db',
},
});
powerSync.init();
class Connector {
async fetchCredentials() {
return {
endpoint: POWERSYNC_ENDPOINT,
token: AUTH_TOKEN,
};
}
async uploadData(database: AbstractPowerSyncDatabase) {
console.log('Uploading data');
}
}
export const setupPowerSync = (): PowerSyncDatabase => {
const connector = new Connector();
powerSync.connect(connector);
return powerSync;
};
export const resetPowerSync = async () => {
await powerSync.disconnectAndClear();
setupPowerSync();
};

Este arquivo faz o seguinte:

  • Cria uma nova classe Connector , que será usada para definir o token de desenvolvimento e o endpoint PowerSync em nosso cliente PowerSync.

  • Define uma função simulada uploadData, que será utilizada na fase seguinte para enviar alterações para o Atlas.

  • Define métodos para configurar e redefinir nosso cliente PowerSync. A redefinição do cliente será útil para o desenvolvimento agora, pois todas as alterações feitas serão colocadas em uma fila. Até que essas alterações sejam processadas, você não receberá novas atualizações.

Em seguida, atualize AppWrapper.tsx para usar o novo método setupPowerSync:

import { PowerSyncContext } from '@powersync/react-native';
import React from 'react';
import { App } from './App';
import { setupPowerSync } from './PowerSync';
const powerSync = setupPowerSync();
export const AppWrapper = () => {
return (
<PowerSyncContext.Provider value={powerSync}>
<App />
</PowerSyncContext.Provider>
);
};

Em seguida, refatore LogoutButton.tsx para implementar o método resetPowerSync. Renomeie-o para ResetButton.tsx e atualize seu conteúdo da seguinte forma:

import React, { useCallback } from 'react';
import { Pressable, Alert, View, Text, StyleSheet } from 'react-native';
import { colors } from './Colors';
import { resetPowerSync } from './PowerSync';
export function ResetButton() {
const signOut = useCallback(() => {
resetPowerSync();
}, []);
return (
<Pressable
onPress={() => {
Alert.alert('Reset Database?', '', [
{
text: 'Yes, Reset Database',
style: 'destructive',
onPress: () => signOut(),
},
{ text: 'Cancel', style: 'cancel' },
]);
}}>
<View style={styles.buttonContainer}>
<Text style={styles.buttonText}>Reset</Text>
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
buttonContainer: {
paddingHorizontal: 12,
},
buttonText: {
fontSize: 16,
color: colors.primary,
},
});

Em seguida, modifique App.tsx para mostrar o botão Reset no lado esquerdo do cabeçalho:

  • Substitua import { LogoutButton } from './LogoutButton'; por import { ResetButton } from './ResetButton';

  • No headerLeft, substitua a linha existente por return <ResetButton />;

  • Descomente a linha //headerLeft para que o botão Redefinir seja exibido.

Suas alterações serão parecidas com o seguinte:

import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { StyleSheet, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { ResetButton } from './ResetButton';
import { ItemListView } from './ItemListView';
import { OfflineModeButton } from './OfflineModeButton';
const Stack = createStackNavigator();
const headerRight = () => {
return <OfflineModeButton />;
};
const headerLeft = () => {
return <ResetButton />;
};
export const App = () => {
return (
<>
{/* All screens nested in RealmProvider have access
to the configured realm's hooks. */}
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Your To-Do List"
component={ItemListView}
options={{
headerTitleAlign: 'center',
headerLeft,
//headerRight,
}}
/>
</Stack.Navigator>
</NavigationContainer>
<View style={styles.footer}>
<Text style={styles.footerText}>
Log in with the same account on another device or simulator to see
your list sync in real time.
</Text>
</View>
</SafeAreaProvider>
</>
);
};
const styles = StyleSheet.create({
footerText: {
fontSize: 12,
textAlign: 'center',
marginVertical: 4,
},
hyperlink: {
color: 'blue',
},
footer: {
paddingHorizontal: 24,
paddingVertical: 12,
},
});

Por último, a biblioteca react-native-dotenv exige que nosso servidor React Native seja redefinido com um cache limpo, o que é normal ao adicionar funcionalidade ao Babel.

Para fazer isso, elimine qualquer instância do React Native atualmente em execução com ctrl-c e, em seguida, insira o seguinte para executar a instância com um cache limpo:

npm start -- --reset-cache

Agora você deve estar pronto para sincronizar seus dados do Atlas com seu aplicação React Native .

Agora redefina o aplicação. Se você fez modificações no banco de dados local do aplicativo antes, precisará clicar no novo botão Reset para redefinir o banco de dados local com o conteúdo do que está armazenado no Atlas.

Você deve agora ver todos os itens da lista de tarefas de mockUserId:

Captura de tela da UI

Se você encontrar problemas, exclua o aplicação em seu emulador/simulador e reconstrua-o para começar do zero.

If you are still encountering issues, you can view the changes made up to this point in the 03-Sync-Data-From-Atlas branch of the example repository.

Agora que seus dados estão sincronizando com o aplicação móvel, a próxima etapa é criar uma maneira de propagar as alterações locais no Atlas.

Nesta fase, você:

  • Implemente o método uploadData em seu Connector

  • Criar um servidor de backend simples para lidar com operações do dispositivo móvel

For the sake of simplicity, this guide will run the server locally. For production use cases, you should consider using a cloud service to handle these requests (e.g. JourneyApps offers serverless cloud functions to help with this).

Comece examinando as operações enviadas para o método uploadData quando são feitas alterações locais no aplicação móvel.

Faça as seguintes alterações em source/PowerSync.ts:

async uploadData(database: AbstractPowerSyncDatabase) {
const batch = await database.getCrudBatch();
console.log('batch', JSON.stringify(batch, null, 2));
}

Em seguida, você fará alterações no aplicação móvel que incluem:

  • Excluindo um item

  • Alternar um item como completo ou incompleto

  • Adicionando um novo item

Conclua a implementação do método uploadData para enviar essas informações em uma solicitação de busca.

Primeiro, adicione um novo valor ao seu .env:

BACKEND_ENDPOINT=http://localhost:8000

e types/env.d.ts:

declare module '@env' {
export const AUTH_TOKEN: string;
export const POWERSYNC_ENDPOINT: string;
export const BACKEND_ENDPOINT: string;
}

Se você estiver usando o emulador Android, deverá garantir que as solicitações para localhost na porta 8000 estejam sendo encaminhadas do emulador para sua máquina local. Para habilitar isso, execute o seguinte comando:

adb reverse tcp:8000 tcp:8000

Em seguida, adicione BACKEND_ENDPOINT à declaração de importação em source/PowerSync.ts:

import { AUTH_TOKEN, POWERSYNC_ENDPOINT, BACKEND_ENDPOINT } from '@env';

Em seguida, atualize o método uploadData:

async uploadData(database: AbstractPowerSyncDatabase) {
const batch = await database.getCrudBatch();
if (batch === null) {
return;
}
const result = await fetch(`${BACKEND_ENDPOINT}/update`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(batch.crud),
});
if (!result.ok) {
throw new Error('Failed to upload data');
}
batch.complete();
}

O método atualizado agora enviará uma matriz de operações CRUD para o endpoint de backend:

  • Se o aplicação estiver offline, ele simplesmente falhará.

  • Se o aplicação receber uma resposta positiva, ele marcará as operações como concluídas e o lote de operações será removido do aplicação móvel.

Agora, crie uma nova pasta no seu projeto chamada backend:

mkdir backend

Em seguida, crie um arquivo package.json:

{
"main": "index.js",
"scripts": {
"start": "node --env-file=.env index.js"
},
"dependencies": {
"express": "^4.21.2",
"mongodb": "^6.12.0"
}
}

Este package.json inclui um script start que adiciona variáveis de um .env ao serviço.

Crie um novo .env com seu Atlas connection string de antes:

MONGODB_URI=<connection_string>

Agora, instale as dependências:

npm install

Observe que este guia não incluirá como adicionar Typescript e outras ferramentas a este serviço, mas você pode fazê-lo à vontade. Além disso, o guia mantém a validação no mínimo e implementa apenas as alterações necessárias para preparar os dados recebidos do aplicação móvel para serem inseridos no MongoDB.

Primeiro, crie um index.js com o seguinte conteúdo:

const express = require("express");
const { MongoClient, ObjectId } = require("mongodb");
const app = express();
app.use(express.json());
// MongoDB setup
const client = new MongoClient(
process.env.MONGODB_URI || "mongodb://localhost:27017",
);
// Helper function to coerce isComplete to boolean
function coerceItemData(data) {
if (data && "isComplete" in data) {
data.isComplete = !!Number(data.isComplete);
}
return data;
}
async function start() {
await client.connect();
const db = client.db("PowerSync");
const items = db.collection("Item");
app.post("/update", async (req, res) => {
const operations = req.body;
try {
for (const op of operations) {
console.log(JSON.stringify(op, null, 2));
switch (op.op) {
case "PUT":
await items.insertOne({
...coerceItemData(op.data),
_id: new ObjectId(op.id),
});
break;
case "PATCH":
await items.updateOne(
{ _id: new ObjectId(op.id) },
{ $set: coerceItemData(op.data) },
);
break;
case "DELETE":
await items.deleteOne({
_id: new ObjectId(op.id),
});
break;
}
}
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(8000, () => {
console.log("Server running on port 8000");
});
}
start().catch(console.error);

Observe a partir do serviço acima que o isComplete é forçado em um valor boolean. Isso garante que os novos itens do todolist cheguem ao MongoDB com true ou false em vez de 1 ou 0. Uma instância ObjectId também está sendo criada a partir do op.id. Definir isso para a propriedade _id moldará os dados de acordo com os requisitos e as melhores práticas do MongoDB .

Agora você pode ativar o servidor:

npm start

O aplicação móvel já deve estar tentando enviar operações para esse endpoint. A declaração console.log deve mostrar as solicitações conforme elas estão sendo enviadas e as alterações devem se propagar para o Atlas.

Você pode verificar isso visualizando sua coleção do MongoDB na UI do Atlas ou no MongoDB Compass.

Captura de tela da UI

Agora você deve ter um aplicação móvel totalmente funcional que sincroniza dados de e para o Atlas. Você também pode tentar desligar o Wi-Fi para testar como o aplicativo funciona quando está offline.

If you encounter issues, you can view the changes made up to this point in the 04-Write-To-Backend branch of the example repository.

Esta fase final aborda como implementar duas funcionalidades opcionais de teste de aplicação, bem como como limpar seu projeto de qualquer código e dependências desnecessários.

No processo de criação deste aplicação, as seguintes funcionalidades foram omitidas: Mostrar Todas as Tarefas e a chave de modo Offline. Esses recursos são úteis para testar a funcionalidade do aplicativo e não se destinam a ser usados em um aplicação de produção.

Observação

As etapas relacionadas a esses recursos são marcadas como opcionais. Sinta-se livre para pular estas etapas opcionais se isso não for do seu interesse.

To implement the optional Show All toggle, a second bucket will be created that will be activated based on a client parameter. You will apply this by disconnecting the current sync session and reconnecting with a new value set. This value will be a boolean called view_all, which will be used as an insecure backdoor to show all the todo list items ever created in the cluster. This functionality helps showcase that the buckets can be dynamically created based on certain parameters.

Observação

A metodologia usada aqui é insegura, portanto, o sinalizador accept_potentially_dangerous_queries precisará ser ativado no bucket para que isso seja feito. Uma maneira segura de fazer isso seria baseá-lo em uma função de usuário e atualizar as autorizações dos usuários em seu banco de dados de apoio, que está fora do escopo deste guia.

Para começar, navegue até o painel do PowerSync e atualize as regras de sincronização para incluir um bucket com base no parâmetro view_all que está sendo definido:

bucket_definitions:
user_buckets:
parameters:
- SELECT request.user_id() as user_id
data:
- SELECT _id as id FROM "Item" WHERE bucket.user_id = 'global'
OR owner_id = bucket.user_id
view_all_bucket:
accept_potentially_dangerous_queries: true
parameters:
- SELECT (request.parameters() ->> 'view_all') as view_all
data:
- SELECT _id as id FROM "Item" WHERE bucket.view_all = true

Observe que as definições de bucket são combinadas juntas, portanto, quando o view_all_bucket estiver ativo, ele será adicionado aos dados do user_buckets.

Em seguida, atualize source/PowerSync.ts em seu projeto para incluir uma variável local para determinar o estado de sinalizador view_all e aplique-o aos parâmetros da instância de conexão.

Primeiro, adicione um parâmetro viewAll e atualize a função setupPowerSync:

let viewAll = false;
export const setupPowerSync = (): PowerSyncDatabase => {
const connector = new Connector();
powerSync.connect(connector, {params: {view_all: viewAll}});
return powerSync;
};

Em seguida, adicione as seguintes duas funções:

export const resetPowerSync = async () => {
await powerSync.disconnectAndClear();
setupPowerSync();
};
export const toggleViewAll = () => {
viewAll = !viewAll;
resetPowerSync();
};

Finalmente, atualize source/ItemListView.tsx.

Primeiro, importe toggleViewAll de PowerSync:

import { toggleViewAll } from './PowerSync';

Em seguida, modifique o atributo onValueChange da opção "Mostrar todas as tarefas" para invocar o método toggleViewAll. Use o seguinte código para substituir os componentes Text e Switch:

<Text style={styles.toggleText}>Show All Tasks</Text>
<Switch
trackColor={{true: '#00ED64'}}
onValueChange={() => {
setShowAllItems(!showAllItems);
toggleViewAll();
}}
value={showAllItems}
/>

Agora reinicie seu aplicação e verifique se ele funciona conforme o esperado:

Captura de tela da UI

Para implementar a alternância opcional do Modo offline, você precisará desconectar a sessão de sincronização e reconectá-la. Isso permitirá que você faça alterações locais enquanto não estiver conectado à sincronização e verifique se elas são enviadas quando a sessão de sincronização for restabelecida.

Você adicionará uma variável para o estado da conexão e, em seguida, criará um método para alternar isso e invocar os métodos connect e disconnect no cliente PowerSync.

Primeiro, adicione o seguinte a source/PowerSync.ts:

let connection = true;
export const toggleConnection = () => {
if (connection) {
powerSync.disconnect();
} else {
setupPowerSync();
}
connection = !connection;
};

Em seguida, refatore o source/OfflineModeButton.tsx para remover a funcionalidade Realm e substitua-a invocando o novo método toggleConnection. Você também precisará adicionar algumas importações:

import { useState } from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';
import { colors } from './Colors';
import {toggleConnection} from './PowerSync';
export function OfflineModeButton() {
const [pauseSync, togglePauseSync] = useState(false);
return (
<Pressable
onPress={() => {
toggleConnection();
togglePauseSync(!pauseSync);
}}>
<Text style={styles.buttonText}>
{pauseSync ? 'Enable Sync' : 'Disable Sync'}
</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
buttonText: {
padding: 12,
color: colors.primary,
},
});

Finalmente, abra o source/App.tsx e descomente o componente headerRight de volta no Stack.Screen do aplicação:

<Stack.Screen
name="Your To-Do List"
component={ItemListView}
options={{
headerTitleAlign: 'center',
headerLeft,
headerRight,
}}
/>

Agora, verifique as atualizações abrindo uma segunda instância do aplicativo e, em seguida, fazendo algumas alterações:

Captura de tela da UI

Finalmente, você pode limpar seu projeto.

Os seguintes arquivos podem ser excluídos com segurança:

  • atlasConfig.json

  • source/WelcomeView.tsx

Você também pode remover as seguintes dependências do seu package.json:

  • @realm/react

  • realm

Este guia deve ter fornecido a você os blocos de construção para iniciar sua viagem de migração para o PowerSync.

Para resumir, seguindo este guia, você deverá ter realizado o seguinte:

  • Implementou um banco de dados MongoDB com dados de amostra

  • Implementou um serviço PowerSync que sincroniza os dados de amostra

  • Aprendou como visualizar e consultar esses dados usando a Ferramenta de Diagnóstico

  • Converteu um aplicação móvel do Device Sync apenas para local

  • Migrado de um banco de dados Realm somente local para o PowerSync

  • Configurar a sincronização do PowerSync com um banco de dados móvel

  • Criou um backend para enviar alterações do cliente PowerSync para MongoDB

Para as próximas etapas, tente pegar um pequeno fragmento do seu aplicação móvel e convertê-lo para usar o PowerSync. E fique atento à documentação futura que aborda casos de uso mais avançados.

Voltar

Migrar para o Amazon Web Services AppSync