Xamarin C# iOS and Android App, GetRealmAsync and SubscribeForNotifications issue

I am building a Xamarin app for iOS and Android. The Realm on a login page is instantiated with the correct credentials from the ContentPage. I used Nito.AsyncEx to manage the context to get the realm, as follows…

      AsyncContext.Run(async () =>
      {
          if (user == null || user.State == Realms.Sync.UserState.LoggedOut)
          {
              user = await app.LogInAsync(credentials);
          }
          var config = new PartitionSyncConfiguration(Constants.Partition, user);
  config.ClientResetHandler =
                   new DiscardLocalResetHandler()
                   {
                       OnBeforeReset = HandleBeforeResetCallback,
                       OnAfterReset = HandleAfterResetCallback,
                       ManualResetFallback = HandleManualResetCallback
                   };
      var  realm = await Realm.GetInstanceAsync(config);
});

…which has been working fine thus far. But doing it this way doesn’t allow me to SubscribeForNotifications to any collections in the sync’d realm.

This works well for initially getting the realm, especially from a new installed version of the app, as it takes time to sync. However, using it looks like Nito.AsyncEx instantiates the Realm in a different Context, and any SubscribeForNotifications won’t receive any notifications, which I need in my app. I have tried lots of Task.Run, Task.Run.ContinueWith, and I’m having zero luck. Is there a way to instantiate the Realm within a NitoAsyncEx Context, then later subscribe for notifications to the Realm in a way that I do receive them? Or is there a way to instantiate the Realm without Nito.AsyncEx, but the LoginAsync, PartitionSyncConfiguration , GetInstanceAsync can be done (via a synchronous wrapper, perhaps?) in the proper order and the results from the two async methods (LoginAsync, GetInstanceAsync) are returned without a thread deadlock on the UI Context?

I may be missing something, but why is it a problem to just call all this code on the main thread instead of using Nito.AsyncEx? You’re mentioning something about a deadlock, but it’s not clear to me what could be causing it since those are all async methods.

I’m not. sure what the cause is, exactly myself. The login screen comes up in the app, and when the user logs in - email, apple sign, or guest, creates the credentials (based on whether the login is thru email, apple sign in, or guest, then I use the function here to to retrieve the realm.

        private void GetRealmCommand(Credentials credentials)
        {
#if DEBUG
            Debug.WriteLine("Entering GetRealmCommand");
#endif
            try
            {
                var app = Realms.Sync.App.Create(Constants.MongodPhotoEventsAppID);
                var user = app.CurrentUser;
                AsyncContext.Run(async () =>
                {
                    if (user == null || user.State == Realms.Sync.UserState.LoggedOut)
                    {
                        user = await app.LogInAsync(credentials);
                    }
                    var config = new PartitionSyncConfiguration(Constants.Partition, user);
                    config.ClientResetHandler =
                                     new DiscardLocalResetHandler()
                                     {
                                         OnBeforeReset = HandleBeforeResetCallback,
                                         OnAfterReset = HandleAfterResetCallback,
                                         ManualResetFallback = HandleManualResetCallback
                                     };

                    var realm = await Realm.GetInstanceAsync(config);
                    MongoRealmServices.User = user;
                    MongoRealmServices.Config = config;
                    MongoRealmServices.Realm = realm;
                });
            }
            catch (Exception e)
            {
#if DEBUG
                Debug.WriteLine("EXCEPTION DURING CONNECTION TO REALM: " + e.ToString());
#endif
                MessagingCenter.Send<LoginPage, string>(this, "GetRealmCommand",
"Exception:\n" + e.ToString());
            }
        }
After this function is called by the event handler (for the guest login, for instance) then Shell.Current.GoToAsync() is called to go to the Main Page, and in the constructor of the Main Page, attempts to access the realm fail. If I setup a property like this in a class, set it from the login page (but only when I use Nito.AsyncEx, as a way to use the realm in the Main Page, it works, but SubscribeToNotifications won't for the Main Page.
namespace StellaEvents.Services
{
    public class MongoRealmServices
    {
        private static Realm realm;
        static public Realm Realm
        {
            get { return realm; }
            set { realm = value; }
        }
}

If I try an use Realm.GetInstanceAsync directly in the Main Page constructor it’s null when the method trying to use Realm.All<> tries to access the Realm. I’m not sure how to get a valid Realm in the MainPage constructor at this point.

So there are several things here. First, you’re using Nito.AsyncEx to make an async method synchronous. That should not be necessary and is probably freezing your app while the login/download is happening. Instead what you could do is something like:

private void GetRealmCommand(Credentials credentials)
{
    GetRealmCommandAsync();
}

private async Task GetRealmCommandAsync()
{
    // Show some loading indicator in the UI
    // Set RealmCommand.CanExecute to false
    try
    {
        var app = App.Create(Constans.MongodPhotoEventsAppID);
        var user = app.CurrentUser;
        if (user == null)
        {
             user = await app.LogInAsync(credentials);
        }

        var config = ...
       
        var realm = await Realm.GetInstanceAsync(config);
    }
    catch (Exception e)
    {

    }
    finally
    {
        // Hide loading indicator
        // Set RealmCommand.CanExecute to true
    }
}

That way you setup everything on the UI thread and can use it in your pages/viewmodels. If you don’t want to do that, then you can still use Nito.AsyncEx, but don’t set the Realm you just downloaded to your MongoRealmServices.Realm. Instead, in the page/viewmodel, you just use the config to open a Realm instance synchronously.

class MainPage()
{
    private Realm _realm;

    public MainPage()
    {
        _realm = Realm.GetInstance(MongoRealmServices.Config);
    }
}

If you want a more thorough deep dive in best practices when using Realm in Xamarin.Forms (although that’s applicable to any xaml-based UI framework), you can check out this blog post by @papafe. It goes into a lot of detail about structuring your app but also compares and contrasts to a SQLite-based app, so if you have experience with that, it will help even more. It does focus on the local database + an http-based API, but the main principles still hold as after the authentication part, there’s pretty much no difference between using a local or a synchronized Realm.

1 Like

you can close this one, the static properties is what was killing it!

Thanks, I will read and apply it going forward.!