How to Use Realm Effectively in a Xamarin.Forms App
Rate this article
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 .
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 . The official documentation for and are valuable resources to learn about these topics.
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.
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 () 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.
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.
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
SharedGroceriesWebServiceproject, and run it. This should start the web service on
http://localhost:5000by default. After that, you can simply run the
SharedGroceriesproject 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—
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.
If we start from the lower part of the architecture schema, we have the
RestAPInamespace that contains the code responsible for the communication with the web service. In particular, the
RestAPIClientis making HTTP requests to the
SharedGroceriesWebService. The data is exchanged in the form of DTOs (), 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:
UserInfoDTOis 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
RealmServiceis 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
GetRealmmethod 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.
DataServiceclass 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:
RetrieveUsersmethod 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
usingdeclaration to dispose of the realm at the end of the try block.
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 (
IDictionary), as it is happening with
Another thing to notice here is that
GroceryItemis 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
ShoppingListthat contains it. This implies that
GroceryItems get deleted when the parent
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.
ShoppingListsCollectionViewModelis 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
Lists. That is a queryable collection of
ShoppingListelements, representing all the shopping lists of the user.
Listsis defined as a public property with a getter, and this allows to bind it to the UI, as we can see in
The content of the page is a
ItemsSourceis bound to
Lists(A). This means that the rows of the
ListVieware 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
TextCellwhose text is bound to the variable
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
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.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,
Listswill 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
OpenListmethods (3) that are invoked, respectively, when the Add button is clicked or when a list is clicked. The
OpenListmethod just passes the clicked
AddListfirst creates a new empty list, adds the current user in the list of owners, adds it to the realm, and then opens the list.
ShoppingListViewModelis 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,
UncheckedItems, that represent, respectively, the list of items that have been checked (purchased) and those that haven't been. In order to obtain those,
AsRealmQueryableis called on
ShoppingList.Items, to convert the
IListto 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
Textis bound to
ShoppingList.Name. This allows the user to read and eventually modify the name of the list.
- (C) A bindable
StackLayoutthat is bound to
UncheckedItems. This is the list of items that need to be purchased. Each of the rows of the
StackLayoutare bound to an element of
UncheckedItems, and thus to a
- (D) A
Buttonthat allows us to add new elements to the list.
- (E) A separator (the
BoxView) and a
Labelthat describe how many elements of the list have been ticked, thanks to the binding to
- (F ) A bindable
StackLayoutthat is bound to
CheckedItems. This is the list of items that have been already purchased. Each of the rows of the
StackLayoutare bound to an element of
CheckedItems, and thus to a
If we focus our attention on on the
DataTemplateof the first bindable
StackLayout, we can see that each row is composed by three elements:
- (H) A
Checkboxthat is bound to
GroceryItem. This allows us to check and uncheck items.
- (I) An
Entrythat is bound to
GroceryItem. This allows us to change the name of the items.
- (J) A
Buttonthat, when clicked, executed the
DeleteItemCommandcommand on the view model, with
GroceryItemas argument. This allows us to delete an item.
Please note that for simplicity, we have decided to use a bindable
StackLayoutto 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
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 (
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
GroceryItemis set to true, thanks to the bindings. This means that this item is no more part of
UncheckedItems(defined as the collection of
Purchasedset 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
Purchasedset to true in the query (1)), and as such it will appear in the bottom list. Given that the number of elements in
CheckedItemshas changed, the text in
Label(E) will be also updated.
Coming back to the view model, we then have the
Deletemethods (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
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
SharedGrocerieshappens 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
StartEditingis called when opening a list in
This method persists to disk the
Idof the list that is being currently edited.
FinishEditingis called in
This method is called when
ShoppingListsCollectionPageappears on screen, and so the user possibly went back from the
ShoppingListsPageafter 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
TrySyncis called both 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
ShoppingListsCollectionViewModelif 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.
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.
Building a Crypto News Website in C# Using the Microsoft Azure App Service and MongoDB Atlas
Jun 13, 2023