Step 1 - My First Realm App

Last updated 16 days ago

Prerequisites

Before we get started we need a few things setup; the prerequisites for this project are:

  • Visual Studio (this guide was written with 7.5.3 - Visual Studio for Mac)

  • You will need your Realm Cloud instance URL that was generated when you created your instance (it can be found by logging in to the cloud portal, and clicking the Copy Instance URL link)

This tutorial should take around 20-30 minutes

Want to get started right away with the complete source code? Check out our Github with ready-to-compile source code. Jump to Step 3 to install the related dependencies and don't forget to update the Constants.cs file with your Realm Cloud or self-hosted instance URL before running the app.

Step 1: Create a New Xamarin Project

Open Visual Studio for Mac and select "New Project..." Then go to Multiplatform>App>Blank Forms App and click Next. Name it "XamarinToDoApp" and make sure both platforms, iOS and Android, are selected and that you are using a .NET Standard shared project. The Organization Identifier can be whatever you want. On the next screen make sure the Project Name and Solution Name are something easy for you to remember and save it somewhere convenient for you and click "Create."

Step 2: Initial Run Test

Before we dive into adding code, it's a good idea to make sure that Visual Studio is configured correctly and that we can successfully compile the app template we just created. In order to do this, select a simulator (for example the iPhoneX) from the build menu, then press the build/run icon.

Step 3: Installing the Realm Frameworks

With the foundation in place, we need to add Realm to it. Our solution has three projects, the Android app, the iOS app, and the .NET Standard library for shared code. Do this by expanding the projects in the solution view, right-clicking on the Packages node, and clicking “Add Packages…”

You will need to add the dependencies for all three projects.

Search for “Realm” in the search box, select it, and add it. Only the single Realm package is needed - adding Realm.Database is not needed and may causes issues on some Windows platforms. In the same nuget pop-up search for the package Acr.UserDialogs and add it to the project as well.

Step 4: Add the FodyWeavers

You may have noticed that your project does not compile and run anymore, we need to add the weavers for this to run. Go to the shared code XamarinToDoApp project, right-click and select Add>New File... then select Empty XML File and name it FodyWeavers

For each Project(shared, Android, iOS) add a blank XML file calledFodyWeavers.xml

FodyWeavers.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Weavers>
<RealmWeaver />
</Weavers>

You should now be able to build and run the app.

Step 5: Adding the Realm Model for ToDo

Go to the shared code XamarinToDoApp project, right-click and select Add>New File... then select Empty Class and name it Item.

Item.cs
using Realms;
using System;
namespace XamarinToDoApp
{
public class Item : RealmObject
{
[PrimaryKey]
[MapTo("itemId")]
public string ItemId { get; set; } = Guid.NewGuid().ToString();
[MapTo("body")]
public string Body { get; set; }
[MapTo("isDone")]
public bool IsDone { get; set; }
[MapTo("timestamp")]
public DateTimeOffset Timestamp { get; set; }
}
}

You should now be able to build and run the app.

Step 6: Setup the User Interface

Now that we have our model in place, we’ll create its corresponding UI. Let’s create a new Page in the portable project, where all of our code will go. Make sure to select Forms > Forms ContentPage XAML in the New File dialog. Name it ItemEntriesPage. This will contain the ListView that displays the items and will be the main screen of the app. Open the ItemEntriesPage.xaml and add the following XML:

ItemEntriesPage.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamarinToDoApp.ItemEntriesPage"
Title="ToDo">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddEntryCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<ListView ItemsSource="{Binding Entries}" x:Name="EntriesListView" ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Body}">
<TextCell.ContextActions>
<MenuItem Text="Delete" IsDestructive="true"
Command="{Binding BindingContext.DeleteEntryCommand, Source={x:Reference EntriesListView}"
CommandParameter="{Binding .}" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>

We’ll set this as the MainPage of the App class that Xamarin.Forms generated for you in App.xaml.cs

App.xaml.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace XamarinToDoApp
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new ItemEntriesPage());
}
}
}

Step 7: Add the ViewModel for the ToDo Page

We’ll also need a ViewModel for our Page, so let’s right-click on the shared project and Add a New Class file just like before called ItemEntriesViewModel.cs

And add the following libraries with the using statement, we will use them later:

ItemEntriesViewModel.cs
using Acr.UserDialogs;
using Realms;
using Realms.Sync;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Credentials = Realms.Sync.Credentials;
namespace XamarinToDoApp
{
public class ItemEntriesViewModel
{
public ItemEntriesViewModel()
{
}
}
}

And wire it up in the page’s code-behind in ItemEntriesPage.xaml.cs

ItemEntriesPage.xaml.cs
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace XamarinToDoApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ItemEntriesPage : ContentPage
{
public ItemEntriesPage()
{
InitializeComponent();
BindingContext = new ItemEntriesViewModel();
}
private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
(sender as ListView).SelectedItem = null;
}
}
}

You can now run the app, but you’ll end up with an empty list and buttons that don't do anything, let’s do something about that!

Step 8: Create a Constants File

Right Click on our Project and Add a new Class called Constants.cs

Constants.cs
namespace XamarinToDoApp
{
public static class Constants
{
// **** Realm Cloud Users:
// **** Replace MY_INSTANCE_ADDRESS with the hostname of your cloud instance
// **** e.g., "mycoolapp.us1.cloud.realm.io"
// ****
// ****
// **** ROS On-Premises Users
// **** Replace the AUTH_URL and REALM_URL strings with the fully qualified versions of
// **** address of your ROS server, e.g.: "http://127.0.0.1:9080" and "realm://127.0.0.1:9080"
public static string AuthUrl = "https://awesome-metal-cheese.us1.cloud.realm.io"; // <- update this
public static string RealmPath = "ToDo";
}
}

Step 9: Bind Realm to the ViewModel

A Realm is an object that encapsulates a database. We’ll let our ViewModel manage the realm. Define Realm at the top below the ItemEntriesViewModel class

ItemEntriesViewModel.cs
using Acr.UserDialogs;
using Realms;
using Realms.Sync;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Credentials = Realms.Sync.Credentials;
namespace XamarinToDoApp
{
public class ItemEntriesViewModel : INotifyPropertyChanged
{
public IEnumerable<Item> Entries { get; private set; }
private Realm _realm;
public event PropertyChangedEventHandler PropertyChanged;
public ICommand AddEntryCommand { get; private set; }
public ItemEntriesViewModel()
{
}
}
}

Now define them the class constructor

ItemEntriesViewModel.cs
public ItemEntriesViewModel()
{
DeleteEntryCommand = new Command<Item>(DeleteEntry);
AddEntryCommand = new Command(() => AddEntry().IgnoreResult());
Initialize().IgnoreResult();
}

Note that we are exposing the available entries by making a simple query to the Realm. Realm.All<>()will give us the entire set of a given type that exists in the Realm. As it is a live query which auto-refreshes with the latest state, we can simply bind our list view to it and it will be continuously up to date.

You may have noticed that VS gives you a compiler error for DeleteEntry does not exist in the current context, let's fix that. To do so, let’s add a Delete context action to the ListView

Go to ItemEntriesPage.xaml and under the TextCell Title view:

ItemEntriesPage.xaml
<TextCell.ContextActions>
<MenuItem Text="Delete" IsDestructive="true"
Command="{Binding BindingContext.DeleteEntryCommand, Source={x:Reference EntriesListView}"
CommandParameter="{Binding .}" />
</TextCell.ContextActions>

We’ll use the command pattern to wire up interactivity between our views and view models. To do so, let’s add a new property to ItemEntriesViewModel

ItemEntriesViewModel.cs
public class ItemEntriesViewModel {
//... Other definitions
public ICommand DeleteEntryCommand { get; private set; }
}

Here, DeleteEntry is a simple method to do the actual deletion and should be added to the ItemEntriesViewModel

ItemEntriesViewModel.cs
private void DeleteEntry(Item entry) => _realm.Write(() => _realm.Remove(entry));

Now we have an app that displays item entries straight from a Realm database using concise XAML and data binding.

Step 10: Add an Initialize Call

Below the ItemEntriesViewModel constructor add a new asynchronous function called Initialize() This function which is called in the ViewModel constructor will open the realm, pull the list of Items, and re-render the Items any time a property changes.

ItemEntriesViewModel.cs
private async Task Initialize()
{
_realm = await OpenRealm();
Entries = _realm.All<Item>().OrderBy(i => i.Timestamp);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Entries)));
}

Step 11: Define the AddEntry Function

Below the Initialize constructor, let's define the AddEntry function. This function uses the Acr.UserDialogs library to show a pop-up to the user which prompts them to type a new item into the textbox. If there is a result it writes this text in a new Item in a realm write transaction.

ItemEntriesViewModel.cs
private async Task AddEntry()
{
var result = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
Title = "New entry",
Message = "Specify the item text",
});
if (result.Ok)
{
_realm.Write(() =>
{
_realm.Add(new Item
{
Timestamp = DateTimeOffset.Now,
Body = result.Value,
});
});
}
}

Step 12: Define the OpenRealm function

The last function we need to add is opening the realm itself. The function first checks to see if there is already a logged in User and if there is just opens the realm and returns. If it does not it uses the UserDialog pop-up for the user to enter in a valid nickname - from there we attempt to open the realm and display a variety of Loading screens as the data is pulled down - all wrapped in a try block.

ItemEntriesViewModel.cs
private async Task<Realm> OpenRealm()
{
var user = User.Current;
if (user != null)
{
var configuration = new FullSyncConfiguration(new Uri(Constants.RealmPath, UriKind.Relative), user);
// User has already logged in, so we can just load the existing data in the Realm.
return Realm.GetInstance(configuration);
}
// When that is called in the page constructor, we need to allow the UI operation
// to complete before we can display a dialog prompt.
await Task.Yield();
var response = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
Title = "Login",
Message = "Please enter your nickname",
OkText = "Login",
IsCancellable = false,
});
var credentials = Credentials.Nickname(response.Value, isAdmin: true);
try
{
UserDialogs.Instance.ShowLoading("Logging in...");
user = await User.LoginAsync(credentials, new Uri(Constants.AuthUrl));
UserDialogs.Instance.ShowLoading("Loading data");
var configuration = new FullSyncConfiguration(new Uri(Constants.RealmPath, UriKind.Relative), user);
// First time the user logs in, let's use GetInstanceAsync so we fully download the Realm
// before letting them interract with the UI.
var realm = await Realm.GetInstanceAsync(configuration);
UserDialogs.Instance.HideLoading();
return realm;
}
catch (Exception ex)
{
await UserDialogs.Instance.AlertAsync(new AlertConfig
{
Title = "An error has occurred",
Message = $"An error occurred while trying to open the Realm: {ex.Message}"
});
// Try again
return await OpenRealm();
}
}

Step 13: Add an Extension

We will get a warning when we attempt to use Tasks that are not called with the await keyword - to get around this lets add a new C# class called Extensions.cs

Extensions.cs
using System;
using System.Threading.Tasks;
namespace XamarinToDoApp
{
public static class Extensions
{
public static void IgnoreResult(this Task task)
{
// This just silences the warnings when tasks are not awaited.
}
}
}

Now build and run your app and start syncing data! Be sure to use Studio to see data synced bidirectionally.