EventGet 50% off your ticket to MongoDB.local NYC on May 2. Use code Web50!Learn more >>
MongoDB Developer
C#
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Languageschevron-right
C#chevron-right

How to Use Realm Effectively in a Xamarin.Forms App

Ferdinando Papale18 min read • Published Feb 07, 2022 • Updated Oct 19, 2022
XamarinRealmC#
Facebook Icontwitter iconlinkedin icon
Rate this article
star-empty
star-empty
star-empty
star-empty
star-empty
Taking care of persistence while developing a mobile application is fundamental nowadays. Even though mobile connection bandwidth, as well as coverage, has been steadily increasing over time, applications still are expected to work offline and in a limited connectivity environment.
This becomes even more cumbersome when working on applications that require a steady stream of data with the service in order to work effectively, such as collaborative applications.
Caching data coming from a service is difficult, but Realm can ease the burden by providing a very natural way of storing and accessing data. This in turn will make the application more responsive and allow the end user to work seamlessly regardless of the connection status.
The aim of this article is to show how to use Realm effectively, particularly in a Xamarin.Forms app. We will take a look at SharedGroceries, an app to share grocery lists with friends and family, backed by a REST API. With this application, we wanted to provide an example that would be simple but also somehow complete, in order to cover different common use cases. The code for the application can be found in the repository here.
Before proceeding, please note that this is not an introductory article to Realm or Xamarin.Forms, so we expect you to have some familiarity with both. If you want to get an introduction to Realm, you can take a look at the documentation for the Realm .NET SDK. The official documentation for Xamarin.Forms and MVVM are valuable resources to learn about these topics.
Build better mobile apps with Atlas Device Sync: Atlas Device Sync is a fully-managed mobile backend-as-a-service. Leverage out-of-the-box infrastructure, data synchronization capabilities, built-in network handling, and much more to quickly launch enterprise-grade mobile apps. Get started now by build: Deploy Sample for Free!

The architecture

In this section, we are going to discuss the difference between the architecture of an application backed by a classic SQL database and the architecture of an application that uses Realm.

Classic architecture

Classic architecture
In an app backed by a classic SQL database, the structure of the application will be similar to the one shown in the diagram, where the arrows represent the dependency between different components of the application. The view model requests data from a repository that can retrieve it both from a remote data source (like a web service) when online and from a local database, depending on the situation. The repository also takes care of keeping the local database up to date with all the data retrieved from the web service. This approach presents some issues:
  • Combining data coming from both the remote data source and the local one is difficult. For example, when opening a view in an application for the first time, it's quite common to show locally cached data while the data coming from a web service is being fetched. In this case, it's not easy to synchronize the retrieval, as well as merge the data coming from both sources to present in the view.
  • The data coming from the local source is static. The objects that are retrieved from the database are generally POCOs (plain old class object) and as such, they do not reflect the current state of the data present in the cache. For example, in order to keep the data shown to the user as fresh as possible, there could be a synchronization process in the background that is continuously retrieving data from the web service and inserting it into the database. It's quite complex to make this data available to the final user of the application, though, as with a classic SQL database we can get fresh data only with a new query, and this needs to be done manually, further increasing the need to coordinate different components of the application.
  • Pagination is hard. Objects are fully loaded from the database upon retrieval, and this can cause performance issues when working with big datasets. In this case, pagination could be required to keep the application performant, but this is not easy to implement.

Realm architecture

Realm architecture
When working with Realm, instead, the structure of the application should be similar to the one in the diagram above.
In this approach, the realm is directly accessed from the view model, and not hidden behind a repository like before. When information is retrieved from the web service, it is inserted into the database, and the view model can update the UI thanks to notifications coming from the realm. In our architecture, we have decided to call DataService the entity responsible for the flow of the data in the application.
There are several advantages to this approach:
  • Single source of truth removes conflicts. Because data is coming only from the realm, then there are no issues with merging and synchronizing data coming from multiple data sources on the UI. For example, when opening a view in an application for the first time, data coming from the realm is shown straight away. In the meantime, data from the web service is retrieved and inserted into the realm. This will trigger a notification in the view model that will update the UI accordingly.
  • Objects and collections are live. This means that the data coming from the realm is always the latest available locally. There is no need to query again the database to get the latest version of the data as with an SQL database.
  • Objects and collections are lazily loaded. This means that there is no need to worry about pagination, even when working with huge datasets.
  • Bindings. Realm works out of the box with data bindings in Xamarin.Forms, greatly simplifying the use of the MVVM pattern.
As you can see in the diagram, the line between the view model and the DataService is dashed, to indicate that is optional. Due to the fact that the view model is showing only data coming from the realm, it does not actually need to have a dependency on the DataService, and the retrieval of data coming from the web service can happen independently. For example, the DataService could continuously request data to the web service to keep the data fresh, regardless of what is being shown to the user at a specific time. This continuous request approach can also be used a SQL database solution, but that would require additional synchronization and queries, as the data coming from the database is static. Sometimes, though, data needs to be exchanged with the web service in consequence of specific actions from the user—for example with pull-to-refresh—and in this case, the view model needs to depend on the DataService.

SharedGroceries app

In this section, we are going to introduce our example application and how to run it.
SharedGroceries is a simple collaborative app that allows you to share grocery lists with friends and family, backed by a REST API. We have decided to use REST as it is quite a common choice and allowed us to create a service easily. We are not going to focus too much on the REST API service, as it is outside of the scope of this article.
Let's take a look at the application now. The screenshots here are taken from the iOS version of the application only, for simplicity:
  • (a) The first page of the application is the login page, where the user can input their username and password to login.
  • (b) After login, the user is presented with the shopping lists they are currently sharing. Additionally, the user can add a new list here.
  • (c) When clicking on a row, it goes to the shopping list page that shows the content of such list. From here, the user can add and remove items, rename them, and check/uncheck them when they have been bought.
To run the app, you first need to run the web service with the REST API. In order to do so, open the SharedGroceriesWebService project, and run it. This should start the web service on http://localhost:5000 by default. After that, you can simply run the SharedGroceries project that contains the code for the Xamarin.Forms application. The app is already configured to connect to the web service at the default address.
For simplicity, we do not cover the case of registering users, and they are all created already on the web service. In particular, there are three predefined users—alice, bob, and charlie, all with password set to 1234—that can be used to access the app. A couple of shopping lists are also already created in the service to make it easier to test the application.

Realm in practice

In this section, we are going to go into detail about the structure of the app and how to use Realm effectively. The structure follows the architecture that was described in the architecture section.

Rest API

If we start from the lower part of the architecture schema, we have the RestAPI namespace that contains the code responsible for the communication with the web service. In particular, the RestAPIClient is making HTTP requests to the SharedGroceriesWebService. The data is exchanged in the form of DTOs (Data Transfer Objects), simple objects used for the serialization and deserialization of data over the network. In this simple app, we could avoid using DTOs, and direcly use our Realm model objects, but it's always a good idea to use specific objects just for the data transfer, as this allows us to have independence between the local persistence model and the service model. With this separation, we don't necessarily need to change our local model in case the service model changes.
Here you have the example of one of the DTOs in the app:
UserInfoDTO is just a container used for the serialization/deserialization of data transmitted in the API calls, and contains methods for converting to and from the local model (in this case, the UserInfo class).

RealmService

RealmService is responsible for providing a reference to a realm:
The class is quite simple at the moment, as we are using the default configuration for the realm. Having a separate class becomes more useful, though, when we have a more complicated configuration for the realm, and we want avoid having code duplication.
Please note that the GetRealm method is creating a new realm instance when it is called. Because realm instances need to be used on the same thread where they have been created, this method can be used from everywhere in our code, without the need to worry about threading issues. It's also important to dispose of realm instances when they are not needed anymore, especially on background threads.

DataService

The DataService class is responsible for managing the flow of data in the application. When needed, the class requests data from the RestAPIClient, and then persists it in the realm. A typical method in this class would look like this:
The RetrieveUsers method is first retrieving the list of users (in the form of DTOs) from the Rest API, and then inserting them into the realm, after a conversion from DTOs to model objects. Here you can see the use of the using declaration to dispose of the realm at the end of the try block.

Realm models

The definition of the model for Realm is generally straightforward, as it is possible to use a simple C# class as a model with very little modifications. In the following snippet, you can see the three model classes that we are using in SharedGroceries:
The models are pretty simple, and strictly resemble the DTO objects that are retrieved from the web service. One of the few caveats when writing Realm model classes is to remember that collections (lists, sets, and dictionaries) need to be declared with a getter only property and the correspondent interface type (IList, ISet, IDictionary), as it is happening with ShoppingList.
Another thing to notice here is that GroceryItem is defined as an EmbeddedObject, to indicate that it cannot exist as an independent Realm object (and thus it cannot have a PrimaryKey), and has the same lifecycle of the ShoppingList that contains it. This implies that GroceryItems get deleted when the parent ShoppingList is deleted.

View models

We will now go through the two main view models in the app, and discuss the most important points. We are going to skip LoginViewModel, as it is not particularly interesting.

ShoppingListsCollectionViewModel

ShoppingListsCollectionViewModel is the view model backing ShoppingListsCollectionPage, the main page of the application, that shows the list of shopping lists for the current user. Let's take a look look at the main elements:
In the constructor of the view model (1), we are initializing realm and also Lists. That is a queryable collection of ShoppingList elements, representing all the shopping lists of the user. Lists is defined as a public property with a getter, and this allows to bind it to the UI, as we can see in ShoppingListsCollectionPage.xaml:
The content of the page is a ListView whose ItemsSource is bound to Lists (A). This means that the rows of the ListView are actually bound to the elements of Lists (that is, a collection of ShoppingList). A little bit down, we can see that each of the rows of the ListView is a TextCell whose text is bound to the variable Name of ShoppingList (B). Together, this means that this page will show a row for each of the shopping lists, with the name of list in the row.
An important thing to know is that, behind the curtains, Realm collections (like Lists, in this case) implement INotifyCollectionChanged, and that Realm objects implement INotifyPropertyChanged. This means that the UI will get automatically updated whenever there is a change in the collection (for example, by adding or removing elements), as well as whenever there is a change in an object (if a property changes). This greatly simplifies using the MVVM pattern, as implementing those interfaces manually is a tedious and error-prone process.
Coming back to ShoppingListsCollectionViewModel, in OnAppearing, we can see how the Realm collection is actually populated. If the page has not been loaded before (2), we call the methods DataService.RetrieveUsers and DataService.RetrieveShoppingLists, that retrieve the list of users and shopping lists from the service and insert them into the realm. Due to the fact that Realm collections are live, Lists will notify the UI that its contents have changed, and the list on the screen will get populated automatically. Note that there are also some more interesting elements here that are related to the synchronization of local data with the web service, but we will discuss them later.
Finally, we have the AddList and OpenList methods (3) that are invoked, respectively, when the Add button is clicked or when a list is clicked. The OpenList method just passes the clicked list to the ShoppingListViewModel, while AddList first creates a new empty list, adds the current user in the list of owners, adds it to the realm, and then opens the list.

ShoppingListViewModel

ShoppingListViewModel is the view model backing ShoppingListPage, the page that shows the content of a certain list and allows us to modify it:
As we will see in a second, the page is binding to two different collections, CheckedItems and UncheckedItems, that represent, respectively, the list of items that have been checked (purchased) and those that haven't been. In order to obtain those, AsRealmQueryable is called on ShoppingList.Items, to convert the IList to a Realm-backed query, that can be queried with LINQ.
The xaml code for the page can be found in ShoppingListPage.xaml. Here is the main content:
This page is composed by an external StackLayout (A) that contains:
  • (B) An Editor whose Text is bound to ShoppingList.Name. This allows the user to read and eventually modify the name of the list.
  • (C) A bindable StackLayout that is bound to UncheckedItems. This is the list of items that need to be purchased. Each of the rows of the StackLayout are bound to an element of UncheckedItems, and thus to a GroceryItem.
  • (D) A Button that allows us to add new elements to the list.
  • (E) A separator (the BoxView) and a Label that describe how many elements of the list have been ticked, thanks to the binding to CheckedItems.Count.
  • (F ) A bindable StackLayout that is bound to CheckedItems. This is the list of items that have been already purchased. Each of the rows of the StackLayout are bound to an element of CheckedItems, and thus to a GroceryItem.
If we focus our attention on on the DataTemplate of the first bindable StackLayout, we can see that each row is composed by three elements:
  • (H) A Checkbox that is bound to Purchased of GroceryItem. This allows us to check and uncheck items.
  • (I) An Entry that is bound to Name of GroceryItem. This allows us to change the name of the items.
  • (J) A Button that, when clicked, executed the DeleteItemCommand command on the view model, with GroceryItem as argument. This allows us to delete an item.
Please note that for simplicity, we have decided to use a bindable StackLayout to display the items of the shopping list. In a production application, it could be necessary to use a view that supports virtualization, such as a ListView or CollectionView, depending on the expected amount of elements in the collection.
An interesting thing to notice is that all the bindings are actually two-ways, so they go both from the view model to the page and from the page to the view model. This, for example, allows the user to modify the name of a shopping list, as well as check and uncheck items. The view elements are bound directly to Realm objects and collections (ShoppingList, UncheckedItems, and CheckedItems), and so all these changes are automatically persisted in the realm.
To make a more complete example about what is happening, let us focus on checking/unchecking items. When the user checks an item, the property Purchased of a GroceryItem is set to true, thanks to the bindings. This means that this item is no more part of UncheckedItems (defined as the collection of GroceryItem with Purchased set to false in the query (1)), and thus it will disappear from the top list. Now the item will be part of CheckedItems (defined as the collection of GroceryItem with Purchased set to true in the query (1)), and as such it will appear in the bottom list. Given that the number of elements in CheckedItems has changed, the text in Label (E) will be also updated.
Coming back to the view model, we then have the AddItem, DeleteItem, and Delete methods (2) that are invoked, respectively, when an item is added, when an item is removed, and when the whole list needs to be removed. The methods are pretty straightforward, and at their core just execute a write transaction modifying or deleting ShoppingList.

Editing and synchronization

In this section, we are going to discuss how shopping list editing is done in the app, and how to synchronize it back to the service.
In a mobile application, there are generally two different ways of approaching editing:
  • Save button. The user modifies what they need in the application, and then presses a save button to persist their changes when satisfied.
  • Continuous save. The changes by the user are continually saved by the application, so there is no need for an explicit save button.
Generally, the second choice is more common in modern applications, and for this reason, it is also the approach that we decided to use in our example.
The main editing in SharedGroceries happens in the ShoppingListPage, where the user can modify or delete shopping lists. As we discussed before, all the changes that are done by the user are automatically persisted in the realm thanks to the two-way bindings, and so the next step is to synchronize those changes back to the web service. Even though the changes are saved as they happen, we decided to synchronize those to the service only after the user is finished with modifying a certain list, and went away from the ShoppingListPage. This allows us to send the whole updated list to the service, instead of a series of individual updates. This is a choice that we made to keep the application simple, but obviously, the requirements could be different in another case.
In order to implement the synchronization mechanism we have discussed, we needed to keep track of which shopping list was being edited at a certain time and which shopping lists have already been edited (and so can be sent to the web service). This is implemented in the following methods from the DataService class:
The method StartEditing is called when opening a list in ShoppingListsCollectionViewModel:
This method persists to disk the Id of the list that is being currently edited.
The method FinishEditing is called in OnAppearing in ShoppingListsCollectionViewModel:
This method is called when ShoppingListsCollectionPage appears on screen, and so the user possibly went back from the ShoppingListsPage after finishing editing. This method removes the identifier of the shopping list that is currently being edited (if it exists)(1), and adds it to the collection of identifiers for lists that are ready to be synced (2). Finally, it calls the method TrySync (3) in another thread.
Finally, the method TrySync is called both in DataService.FinishEditing and in ShoppingListsCollectionViewModel.OnAppearing, as we have seen before. This method takes care of synchronizing all the local changes back to the web service:
  • It first retrieves the ids of the lists that are ready to be synced (4), and then the id of the (eventual) list being edited at the moment (5).
  • Then, for each of the identifiers of the lists ready to be synced (readyForSyncListsId), if the list is being edited right now (6), it just skips this iteration of the loop. Otherwise, it updates the shopping list on the service (7).
  • Finally, if the update was successful, it removes the identifier from the collection of lists that have been edited (8).
This method is called also in OnAppearing of ShoppingListsCollectionViewModel if this is the first time the corresponding page is loaded. We do so as we need to be sure to synchronize data back to the service when the application starts, in case there have been connection issues previously.
Overall, this is probably a very simplified approach to synchronization, as we did not consider several problems that need to be addressed in a production application:
  • What happens if the service is not reachable? What is our retry policy?
  • How do we resolve conflicts on the service when data is being modified by multiple users?
  • How do we respect consistency of the data? How do we make sure that the changes coming from the web service are not overriding the local changes?
Those are only part of the possible issues that can arise when working with synchronization, especially in a collaborative applications like ours.

Conclusion

In this article, we have shown how Realm can be used effectively in a Xamarin.Forms app, thanks to notifications, bindings, and live objects.
The use of Realm as the source of truth for the application greatly simplified the architecture of SharedGroceries and the automatic bindings, together with notifications, also streamlined the implementation of the MVVM pattern.
Nevertheless, synchronization in a collaborative app such as SharedGroceries is still hard. In our example, we have covered only part of the possible synchronization issues that can arise, but you can already see the amount of effort necessary to ensure that everything stays in sync between the mobile application and the web service.
In a following article, we are going to see how we can use Realm Sync to greatly simplify the architecture of the application and resolve our synchronization issues.

Facebook Icontwitter iconlinkedin icon
Rate this article
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Tutorial

Interact with MongoDB Atlas in an AWS Lambda Function Using C#


Jan 23, 2024 | 5 min read
Tutorial

Working with MongoDB Transactions with C# and the .NET Framework


Sep 23, 2022 | 3 min read
Tutorial

Integrate Azure Key Vault with MongoDB Client-Side Field Level Encryption


May 24, 2022 | 9 min read
Tutorial

MongoDB Atlas Search with .NET Blazor for Full-Text Search


Feb 01, 2024 | 6 min read
Table of Contents