Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
C#chevron-right

Como usar o Realm de forma eficaz em um aplicativo Xamarin.Forms

Ferdinando Papale18 min read • Published Feb 07, 2022 • Updated Sep 09, 2024
XamarinRealmC#
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Alguns recursos mencionados abaixo serão descontinuados em 30, 2025 de setembro. Saiba mais.
Atualmente, é fundamental cuidar da persistência ao desenvolver um aplicativo móvel. Embora a largura de banda da conexão móvel, bem como a cobertura, tenha aumentado constantemente ao longo do tempo, ainda se espera que os aplicativos funcionem off-line e em um ambiente de conectividade limitada.
Isso se torna ainda mais trabalhoso ao trabalhar com aplicativos que exigem um fluxo constante de dados com o serviço para funcionar de forma eficaz, como aplicativos de colaboração.
Armazenar dados em cache provenientes de um serviço é difícil, mas o Realm pode facilitar o trabalho fornecendo uma maneira muito natural de armazenar e acessar dados. Isso, por sua vez, tornará o aplicativo mais responsivo e permitirá que o usuário final trabalhe sem problemas, independentemente do status da conexão.
O objetivo deste artigo é mostrar como usar o Realm de forma eficaz, especialmente em um aplicativo Xamarin.Forms. Vamos dar uma olhada no SharedGroceries, um aplicativo para compartilhar listas de compras com amigos e familiares, apoiado por uma REST API. Com esse aplicativo, queríamos fornecer um exemplo que fosse simples, mas também completo, para cobrir diferentes casos de uso comuns. O código do aplicativo pode ser encontrado no repositório aqui.
Antes de prosseguir, observe que este não é um artigo introdutório ao Realm ou Xamarin.Forms, portanto, espera-se que você tenha alguma familiaridade com ambos. Se você deseja obter uma introdução ao Realm, você pode dar uma olhada na documentação do Realm .NET SDK. A documentação oficial do Xamarin.Forms e doMVVM são recursos valiosos para aprender sobre esses tópicos.
Crie melhores aplicativos móveis com o Atlas Device Sync: o Atlas Device Sync é um backend como serviço móvel totalmente gerenciado. Aproveite a infraestrutura pronta para uso, os recursos de sincronização de dados, o manuseio de rede integrado e muito mais para lançar rapidamente aplicativos móveis de nível empresarial. Comece agora mesmo a construir: Implemente amostras grátis!

A arquitetura

Nesta seção, vamos discutir a diferença entre a arquitetura de um aplicativo apoiado por um banco de dados SQL clássico e a arquitetura de um aplicativo que usa o Realm.

Arquitetura clássica

Arquitetura clássica
Em um aplicativo apoiado por um banco de dados SQL clássico, a estrutura do aplicativo será semelhante à mostrada no diagrama, em que as setas representam a dependência entre os diferentes componentes do aplicativo. O modelo de visualização solicita dados de um repositório que pode recuperá-los de uma fonte de dados remota (como um serviço web) quando estiver online e de um banco de dados local, dependendo da situação. O repositório também se encarrega de manter o banco de dados local atualizado com todos os dados recuperados do serviço da Web. Esta abordagem apresenta alguns problemas:
  • Combinar dados provenientes da fonte de dados remota e da local é difícil. Por exemplo, ao abrir uma exibição em um aplicativo pela primeira vez, é bastante comum mostrar dados armazenados em cache localmente enquanto os dados provenientes de um serviço da Web estão sendo obtidos. Nesse caso, não é fácil sincronizar a recuperação, bem como mesclar os dados provenientes de ambas as fontes para apresentá-los na visualização.
  • Os dados provenientes da fonte local são estáticos. Os objetos recuperados do banco de dados geralmente são POCOs (objeto de classe antigo simples) e, como tal, não refletem o estado atual dos dados presentes no cache. Por exemplo, para manter os dados mostrados ao usuário o mais atualizados possível, pode haver um processo de sincronização em segundo plano que está continuamente recuperando dados do serviço Web e inserindo-os no banco de dados. No entanto, é bastante complexo disponibilizar esses dados para o usuário final do aplicativo, pois com um banco de dados SQL clássico podemos obter dados atualizados apenas com uma nova consulta, e isso precisa ser feito manualmente, aumentando ainda mais a necessidade de coordenar diferentes componentes do aplicativo.
  • A paginação é difícil. Os objetos são totalmente carregados do banco de dados após a recuperação, e isso pode causar problemas de desempenho ao trabalhar com grandes conjuntos de dados. Nesse caso, a paginação pode ser necessária para manter o desempenho do aplicativo, mas isso não é fácilde implementar.

Arquitetura de Realm

Arquitetura de Realm
Ao trabalhar com o Realm, em vez disso, a estrutura do aplicativo deve ser semelhante à do diagrama acima.
Nessa abordagem, o realm é acessado diretamente do modelo de visualização, e não fica oculto atrás de um repositório como antes. Quando as informações são recuperadas do serviço da Web, elas são inseridas no banco de dados, e o modelo de visualização pode atualizar a interface do usuário graças às notificações provenientes do realm. Em nossa arquitetura, decidimos chamar de DataService a entidade responsável pelo fluxo de dados no aplicativo.
Existem várias vantagens para essa abordagem:
  • Uma única fonte de verdade remove conflitos. Como os dados são provenientes apenas do realm, não há problemas com a mesclagem e sincronização de dados provenientes de várias fontes de dados na interface do usuário. Por exemplo, ao abrir uma visualização em um aplicativo pela primeira vez, os dados provenientes do realm são mostrados imediatamente. Enquanto isso, os dados do serviço da Web são recuperados e inseridos na região. Isso trigger uma notificação no modelo de exibição que atualizará a interface do usuário de acordo.
  • Objetos e coleções estão ativos. Isso significa que os dados provenientes do reino são sempre os mais recentes disponíveis localmente. Não é necessário consultar novamente o banco de dados para obter a versão mais recente dos dados, como acontece com um banco de dados SQL.
  • Objetos e coleções são carregados preguiçosamente. Isso significa que não há necessidade de se preocupar com a paginação, mesmo ao trabalhar com grandes conjuntos de dados.
  • Vinculações. O Realm funciona imediatamente com vinculações de dados no Xamarin.Forms, simplificando muito o uso do padrão MVVM.
Como você pode ver no diagrama, a linha entre o modelo de visualização e o DataService está pontilhada para indicar que é opcional. Como o modelo de visualização mostra apenas dados provenientes do reino, ele não precisa realmente depender do DataService, e a recuperação de dados provenientes do serviço web pode ocorrer de forma independente. Por exemplo, o DataService pode solicitar dados continuamente ao serviço da Web para mantê-los atualizados, independentemente do que esteja sendo mostrado ao usuário em um horário específico. Essa abordagem de solicitação contínua também pode ser usada em uma solução de banco de dados SQL, mas isso exigiria sincronização e consultas adicionais, pois os dados provenientes do banco de dados são estáticos. No entanto, às vezes, os dados precisam ser trocados com o serviço da web em consequência de ações específicas do usuário - por exemplo, com o pull-to-refresh - e, nesse caso, o modelo de visualização precisa depender do DataService.

Aplicativo SharedGroceries

Nesta seção, vamos apresentar nosso aplicativo de exemplo e como executá-lo.
SharedGroceries é um aplicativo colaborativo simples que permite compartilhar listas de compras com amigos e familiares, apoiado por uma REST API. Decidimos usar o REST, pois é uma escolha bastante comum e nos permitiu criar um serviço facilmente. Não vamos nos concentrar muito no serviço da REST API, pois ele está fora do escopo deste artigo.
Vamos dar uma olhada no aplicativo agora. As capturas de tela aqui foram retiradas apenas da versão do aplicativo para iOS, para simplificar:
  • (a) A primeira página do aplicativo é a página de login, na qual o usuário pode inserir seu nome de usuário e senha para fazer o login.
  • (b) Após o login, o usuário recebe as listas de compras que está compartilhando no momento. Além disso, o usuário pode adicionar uma nova lista aqui.
  • (c) Ao clicar em uma linha, ela vai para a página da lista de compras que mostra o conteúdo dessa lista. A partir daqui, o usuário pode adicionar e remover itens, renomeá-los e marcá-los / desmarcá-los quando forem comprados.
Para executar o aplicativo, primeiro você precisa executar o serviço da web com a REST API. Para fazer isso, abra o projetoSharedGroceriesWebService e execute-o. Isso deve iniciar o serviço da web em http://localhost:5000 por padrão. Depois disso, você pode simplesmente executar o projetoSharedGroceries que contém o código para o aplicativo Xamarin.Forms. A aplicação já está configurada para se conectar ao serviço web no endereço padrão.
Para simplificar, não abordamos o caso de usuários registrados, e todos eles já são criados no serviço web. Em particular, há três usuários predefinidos —alice, bobe charlie, todos com senha definida como 1234—que podem ser usados para acessar o aplicativo. Algumas listas de compras também já foram criadas no serviço para facilitar o teste do aplicativo.

Realm na prática

Nesta seção, entraremos em detalhes sobre a estrutura do aplicativo e como usar o Realm de forma eficaz. A estrutura segue a arquitetura descrita na seção de arquitetura.

Rest API

Se começarmos da parte inferior do esquema de arquitetura, temos o namespaceRestAPI que contém o código responsável pela comunicação com o serviço web. Em particular, o RestAPIClient está fazendo solicitações HTTP para o SharedGroceriesWebService. Os dados são trocados na forma de DTOs (Data Transfer Objects), objetos simples usados para a serialização e desserialização de dados pela rede. Nesse aplicativo simples, poderíamos evitar o uso de DTOs e usar diretamente nossos objetos de modelo de Realm, mas é sempre uma boa ideia usar objetos específicos apenas para a transferência de dados, pois isso nos permite ter dependência entre o modelo de persistência local e o serviço modelo. Com essa separação, não precisamos necessariamente alterar nosso modelo local caso o modelo de serviço mude.
Aqui está o exemplo de um dos DTOs no aplicativo:
1public class UserInfoDTO
2{
3 public Guid Id { get; set; }
4 public string Name { get; set; }
5
6 public UserInfo ToModel()
7 {
8 return new UserInfo
9 {
10 Id = Id,
11 Name = Name,
12 };
13 }
14
15 public static UserInfoDTO FromModel(UserInfo user)
16 {
17 return new UserInfoDTO
18 {
19 Id = user.Id,
20 Name = user.Name,
21 };
22 }
23}
UserInfoDTO é apenas um container usado para a serialização/deserialização de dados transmitidos nas chamadas de API e contém métodos para converter de e para o modelo local (neste caso, a classeUserInfo).

RealmService

RealmService é responsável por fornecer uma referência a um realm:
1public static class RealmService
2{
3 public static Realm GetRealm() => Realm.GetInstance();
4}
A classe é bastante simples no momento, pois estamos usando a configuração padrão para o reino. No entanto, ter uma classe separada se torna mais útil quando temos uma configuração mais complicada para o reino e queremos evitar a duplicação de código.
Observe que o métodoGetRealmestá criando uma nova instância de realm quando é chamado. Como as instâncias de realm precisam ser usadas no mesmo thread em que foram criadas, esse método pode ser usado em qualquer lugar do nosso código, sem a necessidade de se preocupar com problemas de threading. Também é importante descartar as instâncias de realm quando elas não forem mais necessárias, especialmente em threads em segundo plano.

Data Service

A classeDataService é responsável por gerenciar o fluxo de dados no aplicativo. Quando necessário, a classe solicita dados de RestAPICliente, em seguida, os mantém no domínio. Um método típico desta classe seria assim:
1public static async Task RetrieveUsers()
2{
3 try
4 {
5 //Retrieve data from the API
6 var users = await RestAPIClient.GetAllUsers();
7
8 //Persist data in Realm
9 using var realm = RealmService.GetRealm();
10 realm.Write(() =>
11 {
12 realm.Add(users.Select(u => u.ToModel()), update: true);
13 });
14 }
15 catch (HttpRequestException) //Offline/Service is not reachable
16 {
17 }
18}
O método RetrieveUsersprimeiro recupera a lista de usuários (na forma de DTOs) da REST API e depois os insere no realm, após uma conversão de DTOs para objetos de modelo. Aqui você pode ver o uso da declaraçãousing para descartar o realm no final do bloco try.

Modelos do Realm

A definição do modelo para o Realm geralmente é direta, pois é possível usar uma classe C# simples como modelo com pouquíssimos modificações. No trecho a seguir, você pode ver as três classes de modelo que estamos usando em SharedGroceries:
1public class UserInfo : RealmObject
2{
3 [PrimaryKey]
4 public Guid Id { get; set; }
5 public string Name { get; set; }
6}
7
8public class GroceryItem : EmbeddedObject
9{
10 public string Name { get; set; }
11 public bool Purchased { get; set; }
12}
13
14public class ShoppingList : RealmObject
15{
16 [PrimaryKey]
17 public Guid Id { get; set; } = Guid.NewGuid();
18 public string Name { get; set; }
19 public ISet<UserInfo> Owners { get; }
20 public IList<GroceryItem> Items { get; }
21}
Os modelos são bem simples e se assemelham estritamente aos objetos DTO recuperados do serviço web. Uma das poucas advertências ao escrever classes de modelo de Realm é lembrar que as collections (listas, conjuntos e dicionários) precisam ser declaradas com uma propriedade somente de obtenção e o tipo de interface correspondente (IList, ISet, IDictionary) , como está ocorrendo com ShoppingList.
Outra coisa a notar aqui é que GroceryItem é definido como EmbeddedObject, para indicar que ele não pode existir como um objeto de Realm independente (e, portanto, não pode ter um PrimaryKey) e tem o mesmo ciclo de vida do ShoppingList que o contém. Isso implica que GroceryItems são excluídos quando oShoppingListpai é excluído.

Visualizar modelos

Agora, examinaremos os dois principais modelos de visualização do aplicativo e discutiremos os pontos mais importantes. Vamos pular LoginViewModel, pois ele não é particularmente interessante.

ShoppingListsCollectionViewModel

ShoppingListsCollectionViewModel é o modelo de visualização de suporte ShoppingListsCollectionPage, a página principal do aplicativo, que mostra a lista de listas de compras do usuário atual. Vamos dar uma olhada nos principais elementos:
1public class ShoppingListsCollectionViewModel : BaseViewModel
2{
3 private readonly Realm realm;
4 private bool loaded;
5
6 public ICommand AddListCommand { get; }
7 public ICommand OpenListCommand { get; }
8
9 public IEnumerable<ShoppingList> Lists { get; }
10
11 public ShoppingList SelectedList
12 {
13 get => null;
14 set
15 {
16 OpenListCommand.Execute(value);
17 OnPropertyChanged();
18 }
19 }
20
21 public ShoppingListsCollectionViewModel()
22 {
23 //1
24 realm = RealmService.GetRealm();
25 Lists = realm.All<ShoppingList>();
26
27 AddListCommand = new AsyncCommand(AddList);
28 OpenListCommand = new AsyncCommand<ShoppingList>(OpenList);
29 }
30
31 internal override async void OnAppearing()
32 {
33 base.OnAppearing();
34
35 IDisposable loadingIndicator = null;
36
37 try
38 {
39 //2
40 if (!loaded)
41 {
42 //Page is appearing for the first time, sync with service
43 //and retrieve users and shopping lists
44 loaded = true;
45 loadingIndicator = DialogService.ShowLoading();
46 await DataService.TrySync();
47 await DataService.RetrieveUsers();
48 await DataService.RetrieveShoppingLists();
49 }
50 else
51 {
52 DataService.FinishEditing();
53 }
54 }
55 catch
56 {
57 await DialogService.ShowAlert("Error", "Error while loading the page");
58 }
59 finally
60 {
61 loadingIndicator?.Dispose();
62 }
63 }
64
65 //3
66 private async Task AddList()
67 {
68 var newList = new ShoppingList();
69 newList.Owners.Add(DataService.CurrentUser);
70 realm.Write(() =>
71 {
72 return realm.Add(newList, true);
73 });
74
75 await OpenList(newList);
76 }
77
78 private async Task OpenList(ShoppingList list)
79 {
80 DataService.StartEditing(list.Id);
81 await NavigationService.NavigateTo(new ShoppingListViewModel(list));
82 }
83}
No construtor do modelo de visualização (1), estamos inicializando realm e também Lists. Essa é uma collection consultável de ShoppingList elementos, representando todas as listas de compras do usuário. Lists é definido como uma propriedade pública com um getter, e isso permite vinculá-lo à UI, como podemos ver em ShoppingListsCollectionPage.xaml:
1<ContentPage.Content>
2 <!--A-->
3 <ListView ItemsSource="{Binding Lists}"
4 SelectionMode="Single"
5 SelectedItem="{Binding SelectedList, Mode=TwoWay}">
6 <ListView.ItemTemplate>
7 <DataTemplate>
8 <!--B-->
9 <TextCell Text="{Binding Name}" />
10 </DataTemplate>
11 </ListView.ItemTemplate>
12 </ListView>
13</ContentPage.Content>
O conteúdo da página é um ListView cujo ItemsSource está vinculado a Lists (A). Isso significa que as linhas do ListView estão realmente vinculadas aos elementos de Lists (ou seja, uma coleção de ShoppingList). Um pouco mais abaixo, podemos ver que cada uma das linhas do ListView é um TextCell cujo texto está vinculado à variável Name de ShoppingList (B). Juntos, isso significa que esta página mostrará uma linha para cada uma das listas de compras, com o nome da lista na linha.
Um aspecto importante a saber é que, por trás das cortinas, as coleções Realm (como Lists, neste caso) implementam INotifyCollectionChanged e que os objetos Realm implementam INotifyPropertyChanged. Isso significa que a interface do usuário será atualizada automaticamente sempre que houver uma alteração na coleção (por exemplo, adicionando ou removendo elementos), bem como sempre que houver uma alteração em um objeto (se uma propriedade for alterada). Isso simplifica muito o uso do padrão MVVM, pois implementar essas interfaces manualmente é um processo tedioso e sujeito a erros.
Retornando a ShoppingListsCollectionViewModel, em OnAppearing, podemos ver como a collection Realm é realmente preenchida. Se a página não tiver sido carregada antes (2), chamamos os métodos DataService.RetrieveUsers e DataService.RetrieveShoppingLists, que recuperam a lista de usuários e as listas de compras do serviço e as inserem no Realm. Devido ao fato de as collections do Realm estarem ativas, Lists notificará a UI de que seu conteúdo foi alterado, e a lista na tela será preenchida automaticamente. Observe que há também alguns elementos mais interessantes aqui relacionados à sincronização de dados locais com o serviço web, mas os discutiremos mais tarde.
Por fim, temos os AddList OpenList métodos e3( ) que são invocados, respectivamente, quando o botãoAdicionar é clicado ou quando uma lista é clicada. O OpenList método apenas passa o clicado list para o ShoppingListViewModel, enquanto AddList primeiro cria uma nova lista vazia, adiciona o usuário atual na lista de proprietários, adiciona-o ao domínio e depois abre a lista .

ShoppingListViewModel

ShoppingListViewModel é o modelo de visualização de apoio ShoppingListPage, a página que mostra o conteúdo de uma determinada lista e nos permite modificá-la:
1public class ShoppingListViewModel : BaseViewModel
2{
3 private readonly Realm realm;
4
5 public ShoppingList ShoppingList { get; }
6 public IEnumerable<GroceryItem> CheckedItems { get; }
7 public IEnumerable<GroceryItem> UncheckedItems { get; }
8
9 public ICommand DeleteItemCommand { get; }
10 public ICommand AddItemCommand { get; }
11 public ICommand DeleteCommand { get; }
12
13 public ShoppingListViewModel(ShoppingList list)
14 {
15 realm = RealmService.GetRealm();
16
17 ShoppingList = list;
18
19 //1
20 CheckedItems = ShoppingList.Items.AsRealmQueryable().Where(i => i.Purchased);
21 UncheckedItems = ShoppingList.Items.AsRealmQueryable().Where(i => !i.Purchased);
22
23 DeleteItemCommand = new Command<GroceryItem>(DeleteItem);
24 AddItemCommand = new Command(AddItem);
25 DeleteCommand = new AsyncCommand(Delete);
26 }
27
28 //2
29 private void AddItem()
30 {
31 realm.Write(() =>
32 {
33 ShoppingList.Items.Add(new GroceryItem());
34 });
35 }
36
37 private void DeleteItem(GroceryItem item)
38 {
39 realm.Write(() =>
40 {
41 ShoppingList.Items.Remove(item);
42 });
43 }
44
45 private async Task Delete()
46 {
47 var confirmDelete = await DialogService.ShowConfirm("Deletion",
48 "Are you sure you want to delete the shopping list?");
49
50 if (!confirmDelete)
51 {
52 return;
53 }
54
55 var listId = ShoppingList.Id;
56 realm.Write(() =>
57 {
58 realm.Remove(ShoppingList);
59 });
60
61 await NavigationService.GoBack();
62 }
63}
Como verá em um segundo, a página está vinculada a duas collection diferentes, CheckedItems e UncheckedItems, que representam, respectivamente, a lista de itens que foram verificados (comprados) e aqueles que não foram. Para obtê-los, AsRealmQueryable é chamado em ShoppingList.Items, para converter IList em uma query apoiada pelo Realm, que pode ser consultada com LINQ.
O código xaml da página pode ser encontrado em ShoppingListPage.xaml. Aqui está o conteúdo principal:
1<ContentPage.Content>
2 <ScrollView>
3 <!--A-->
4 <StackLayout Orientation="Vertical" Margin="20" Spacing="10">
5 <!--B-->
6 <Editor Text="{Binding ShoppingList.Name}" HorizontalOptions="Fill"
7 Placeholder="Shopping List Name"/>
8 <!--C-->
9 <StackLayout BindableLayout.ItemsSource="{Binding UncheckedItems}">
10 <BindableLayout.ItemTemplate>
11 <DataTemplate>
12 <!--G-->
13 <StackLayout Orientation="Horizontal">
14 <!--H-->
15 <CheckBox IsChecked="{Binding Purchased}" VerticalOptions="Center" />
16 <!--I-->
17 <Entry Text="{Binding Name}"
18 Keyboard="Email"
19 HorizontalOptions="FillAndExpand"
20 VerticalOptions="Center"/>
21 <!--J-->
22 <Button Text="X" FontSize="Title"
23 Command="{Binding Path=BindingContext.DeleteItemCommand, Source={x:Reference page}}"
24 CommandParameter="{Binding .}"/>
25 </StackLayout>
26 </DataTemplate>
27 </BindableLayout.ItemTemplate>
28 </StackLayout>
29 <!--D-->
30 <Button Text="+ Add Item" Command="{Binding AddItemCommand}"/>
31 <!--E-->
32 <BoxView Color="Gray" HeightRequest="1"/>
33 <Label Text="{Binding CheckedItems.Count, StringFormat='{0} ticked'}"/>
34 <!--F-->
35 <StackLayout BindableLayout.ItemsSource="{Binding CheckedItems}">
36 <BindableLayout.ItemTemplate>
37 <DataTemplate>
38 <StackLayout Orientation="Horizontal">
39 <CheckBox IsChecked="{Binding Purchased}" VerticalOptions="Center" />
40 <Label Text="{Binding Name}" TextDecorations="Strikethrough"
41 HorizontalOptions="FillAndExpand"
42 VerticalOptions="Center"/>
43 <Button Text="X" FontSize="Title"
44 Command="{Binding Path=BindingContext.DeleteItemCommand, Source={x:Reference page}}"
45 CommandParameter="{Binding .}"/>
46 </StackLayout>
47 </DataTemplate>
48 </BindableLayout.ItemTemplate>
49 </StackLayout>
50 </StackLayout>
51 </ScrollView>
52</ContentPage.Content>
Esta página é composta por um StackLayoutexterno (A) que contém:
  • (B) Um Editor cujo Text está vinculado a ShoppingList.Name. Isso permite ao usuário ler e, eventualmente, modificar o nome da lista.
  • (C) Um StackLayoutvinculável que está vinculado a UncheckedItems. Essa é a lista de itens que precisam ser comprados. Cada uma das linhas de StackLayout está vinculada a um elemento de UncheckedItems e, portanto, a um GroceryItem.
  • (D) Um Button que nos permite adicionar novos elementos à lista.
  • (E) Um separador (o BoxView) e um Label que descrevem quantos elementos da lista foram marcados, graças à vinculação com CheckedItems.Count.
  • (F) Um vinculável vinculado StackLayout a CheckedItems. Essa é a lista de itens que já foram comprados. Cada uma das linhas do StackLayout está vinculada a um elemento CheckedItems e, portanto, a GroceryItem.
Se focarmos nossa atenção no DataTemplate do primeiro StackLayoutvinculável, podemos ver que cada linha é composta por três elementos:
  • (H) Um Checkbox que está vinculado a Purchased de GroceryItem. Isso nos permite marcar e desmarcar itens.
  • (I) Um Entry que está vinculado a Name de GroceryItem. Isso nos permite alterar o nome dos itens.
  • (J) Um Button que, quando clicado, executou o comandoDeleteItemCommand no modelo de visualização, com GroceryItem como argumento. Isso nos permite excluir um item.
Observe que, para simplificar, decidimos usar um StackLayoutvinculável para exibir os itens da lista de compras. Em um aplicativo de produção, pode ser necessário usar uma visualização que ofereça suporte à virtualização, como ListView ou CollectionView, dependendo da quantidade esperada de elementos na collection.
Um aspecto interessante a ser observado é que todos os vínculos são, na verdade, bidirecionais, ou seja, vão do modelo de visualização para a página e da página para o modelo de visualização. Isso, por exemplo, permite que o usuário modifique o nome de uma lista de compras, bem como marque e desmarque itens. Os elementos de visualização são vinculados diretamente aos objetos e collection do Realm (ShoppingList, UncheckedItems e CheckedItems) e, portanto, todas essas alterações são automaticamente mantidas no realm.
Para fazer um exemplo mais completo sobre o que está acontecendo, vamos nos concentrar em verificar/desmarcar itens. Quando o usuário verifica um item, a propriedade Purchased de um GroceryItem é definida como true, graças às ligações. Isso significa que esse item não faz mais parte de UncheckedItems (definido como a coleção de GroceryItem com Purchased definido como false na consulta (1)) e, portanto, desaparecerá da lista superior. Agora o item fará parte de CheckedItems (definido como a coleção de GroceryItem com Purchased definido como true na consulta (1)), e como tal aparecerá na lista inferior. Dado que o número de elementos em CheckedItems foi alterado, o texto em Label (E) também será atualizado.
Voltando ao modelo de exibição, temos os métodosAddItem, DeleteIteme Delete (2) que são invocados, respectivamente, quando um item é adicionado, quando um item é removido e quando toda a lista precisa ser removida. Os métodos são bastante diretos e, em sua essência, basta executar uma transação de gravação modificando ou excluindo ShoppingList.

Edição e sincronização

Nesta seção, discutiremos como a edição da lista de compras é feita no aplicativo e como sincronizá-la de volta ao serviço.
Em um aplicativo móvel, geralmente há duas maneiras diferentes de abordar a edição:
  • Botão Salvar. O usuário modifica o que precisa no aplicativo e, em seguida, pressiona um botão salvar para persistir suas alterações quando estiver satisfeito.
  • Salvamento contínuo. As alterações feitas pelo usuário são salvas continuamente pelo aplicativo, portanto, não há necessidade de um botão de salvamento explícito.
Em geral, a segunda opção é mais comum em aplicativos modernos e, por esse motivo, é também a abordagem que decidimos usar em nosso exemplo.
A edição principal em SharedGroceries acontece no ShoppingListPage, onde o usuário pode modificar ou excluir listas de compras. Como discutimos anteriormente, todas as alterações feitas pelo usuário são automaticamente persistidas no domínio graças às ligações bidirecionais e, portanto, a próxima etapa é sincronizar essas alterações de volta ao serviço Web. Mesmo que as alterações sejam salvas à medida que acontecem, decidimos sincronizá-las com o serviço somente depois que o usuário terminar de modificar uma determinada lista e sair do ShoppingListPage. Isso nos permite enviar toda a lista atualizada para o serviço, em vez de uma série de atualizações individuais. Esta é uma escolha que fizemos para manter o aplicativo simples, mas obviamente, os requisitos podem ser diferentes em outro caso.
Para implementar o mecanismo de sincronização que discutimos, precisamos acompanhar qual lista de compras estava sendo editada em um determinado momento e quais listas de compras já foram editadas (e, portanto, podem ser enviadas para o serviço da Web). Isso é implementado nos seguintes métodos da classeDataService:
1public static void StartEditing(Guid listId)
2{
3 PreferencesManager.SetEditingListId(listId);
4}
5
6public static void FinishEditing()
7{
8 var editingListId = PreferencesManager.GetEditingListId();
9
10 if (editingListId == null)
11 {
12 return;
13 }
14
15 //1
16 PreferencesManager.RemoveEditingListId();
17 //2
18 PreferencesManager.AddReadyForSyncListId(editingListId.Value);
19
20 //3
21 Task.Run(TrySync);
22}
23
24public static async Task TrySync()
25{
26 //4
27 var readyForSyncListsId = PreferencesManager.GetReadyForSyncListsId();
28
29 //5
30 var editingListId = PreferencesManager.GetEditingListId();
31
32 foreach (var readyForSyncListId in readyForSyncListsId)
33 {
34 //6
35 if (readyForSyncListId == editingListId) //The list is still being edited
36 {
37 continue;
38 }
39
40 //7
41 var updateSuccessful = await UpdateShoppingList(readyForSyncListId);
42 if (updateSuccessful)
43 {
44 //8
45 PreferencesManager.RemoveReadyForSyncListId(readyForSyncListId);
46 }
47 }
48}
O método StartEditing é chamado ao abrir uma lista em ShoppingListsCollectionViewModel:
1private async Task OpenList(ShoppingList list)
2{
3 DataService.StartEditing(list.Id);
4 await NavigationService.NavigateTo(new ShoppingListViewModel(list));
5}
Esse método persiste no disco do Id da lista que está sendo editada no momento.
O método FinishEditing é chamado em OnAppearing em ShoppingListsCollectionViewModel:
1internal override async void OnAppearing()
2{
3 base.OnAppearing();
4
5 if (!loaded)
6 {
7 ....
8 await DataService.TrySync();
9 ....
10 }
11 else
12 {
13 DataService.FinishEditing();
14 }
15 }
16
17}
Esse método é chamado quando ShoppingListsCollectionPage aparece na tela e, em seguida, o usuário possivelmente retornou do ShoppingListsPage após terminar a edição. Esse método remove o identificador da lista de compras que está sendo editada (se existir)(1) e o adiciona à coleção de identificadores para listas que estão prontas para serem sincronizadas (2). Finalmente, ele chama o método TrySync (3) em outro thread.
Finalmente, o método TrySync é chamado em DataService.FinishEditing e em ShoppingListsCollectionViewModel.OnAppearing, como vimos antes. Este método se encarrega de sincronizar todas as alterações locais de volta ao serviço web:
  • Primeiro, ele recupera os IDs das listas que estão prontas para serem sincronizadas (4) e, em seguida, o ID da lista (final) que está sendo editada no momento (5).
  • Em seguida, para cada um dos identificadores das listas prontas para serem sincronizadas (readyForSyncListsId), se a lista estiver sendo editada agora (6), ele simplesmente pulará essa iteração do loop. Caso contrário, ele atualiza a lista de compras no serviço (7).
  • Por fim, se a atualização tiver sido bem-sucedida, ela removerá o identificador da collection de listas que foram editadas (8).
Este método também é chamado em OnAppearing de ShoppingListsCollectionViewModel se esta for a primeira vez que a página correspondente é carregada. Fazemos isso porque precisamos sincronizar os dados de volta com o serviço quando o aplicativo for iniciado, caso tenha ocorrido problemas de conexão anteriormente.
Em geral, essa é provavelmente uma abordagem muito simplificada da sincronização, pois não consideramos vários problemas que precisam ser resolvidos em um aplicativo de produção:
  • O que acontece se o serviço não estiver acessível? Qual é a nossa política de novas tentativas?
  • Como resolvemos conflitos no serviço quando os dados estão sendo modificados por vários usuários?
  • Como respeitamos a consistência dos dados? Como podemos garantir que as alterações provenientes do serviço Web não estejam substituindo as alterações locais?
Esses são apenas parte dos possíveis problemas que podem surgir ao trabalhar com sincronização, especialmente em aplicativos de colaboração como o nosso.

Conclusão

Neste artigo, mostramos como o Realm pode ser usado de forma eficaz em um aplicativo Xamarin.Forms, graças a notificações, vinculações e objetos ativos.
O uso do Realm como fonte da verdade para o aplicativo simplificou muito a arquitetura do SharedGroceries e as vinculações automáticas, juntamente com as notificações, também simplificaram a implementação do padrão MVVM.
No entanto, a sincronização em um aplicativo participativo como o SharedGroceries ainda é difícil. Em nosso exemplo, cobrimos apenas parte dos possíveis problemas de sincronização que podem surgir, mas você já pode ver a quantidade de esforço necessária para garantir que tudo permaneça sincronizado entre o aplicativo móvel e o serviço da Web.
Em um artigo a seguir, vamos ver como podemos usar o Realm Sync para simplificar muito a arquitetura do aplicativo e resolver nossos problemas de sincronização.

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Crie um website de notícias sobre criptomoedas em C# usando o Microsoft Azure App Service e o MongoDB Atlas


Jun 13, 2023 | 9 min read
Tutorial

Crie uma API RESTful com .NET Core e MongoDB


Sep 11, 2024 | 8 min read
Tutorial

Queries geoespaciais do MongoDB em C#


May 12, 2022 | 11 min read
Tutorial

Salvando dados no Unity3D usando o PlayPrefs


Sep 09, 2024 | 11 min read
Sumário
  • A arquitetura