Strange behavior when monitoring CollectionChanged events in Realm .NET SDK with flexible sync in console/test environment

I am working on .NET MAUI application that uses Realm .NET SDK with flexible sync.
When we create realm instance, we populate initial subscriptions. In the UI, we bind the collection view to the result of realm query. Of course, it takes some time until the data gets synced. Not to show an empty list to the user, we show a loading indicator and subscribe to the CollectionChanged event of the realm query result. Once the data has arrived, we hide the loading indicator and show the data.

Here’s a test class I’ve created to demonstrate the idea in rough steps as a demo

public static class TestFlexibleSync
{
    public static async Task RunTest(string realmDirectory, Func<Task<bool>> continueMonitoring)
    {
        var appConfiguration = new AppConfiguration("REALM_APP_ID");
        var app = App.Create(appConfiguration);
        await app.LogInAsync(Credentials.Anonymous());

        if (Directory.Exists(realmDirectory))
        {
            Directory.Delete(realmDirectory, true);
        }

        Directory.CreateDirectory(realmDirectory);

        using var realm = CreateRealm(app, realmDirectory);
        var planets = realm.All<Planet>();
        planets.AsRealmCollection().CollectionChanged += (sender, args) =>
        {
            Console.WriteLine($"Planets changed: {args.Action}");
            PrintCurrentPlanets(planets);
        };

        while (await continueMonitoring())
        {
            Console.WriteLine("Current planets from cached query");
            PrintCurrentPlanets(planets);

            Console.WriteLine("Current planets from new query");
            using var newRealm = CreateRealm(app, realmDirectory);
            PrintCurrentPlanets(newRealm.All<Planet>());
        }
    }

    private static void PrintCurrentPlanets(IEnumerable<Planet> queryable)
    {
        var currentPlanets = queryable.ToList().Select(p => new
        {
            p.Id,
            p.Name
        }).ToList();
        Console.WriteLine(JsonSerializer.Serialize(currentPlanets, new JsonSerializerOptions { WriteIndented = true }));
    }

    private static Realm CreateRealm(App application, string parentFolder)
    {
        var databasePath = Path.Combine(parentFolder, "database.realm");
        var realmConfiguration = new FlexibleSyncConfiguration(application.CurrentUser!, databasePath)
        {
            Schema = new[] { typeof(Planet) },
            PopulateInitialSubscriptions = r =>
                r.Subscriptions.Add(r.All<Planet>(), new SubscriptionOptions { Name = "AllPlanets" })
        };


        var realmInstance = Realm.GetInstance(realmConfiguration);

        return realmInstance;
    }
}

When I run this code inside .NET MAUI application, as soon as the planets are synced, the CollectionChanged event is invoked and the planets are printed in the console.

In order to validate this behavior, we have E2E test XUnit project. And I have problems with change notifications there. I know, that change notifications require synchronization context and according to the docs, I can supply one using the Nito.AsyncEx library. Here’s how I do that

AsyncContext.Run(async () =>
{
    var realmDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Realm");
    await TestFlexibleSync.RunTest(realmDirectory, ContinueMonitoring);
});

return;

Task<bool> ContinueMonitoring()
{
    return Task.FromResult(!Prompt.GetYesNo("Press any key to print planets. Press Y to exit", false,
        ConsoleColor.Red));
}

As you can see, the test code is executed inside the AsyncContext.Run lambda, which in theory should supply SynchronizationContext and enable change notifications. However, that’s not the behavior I observe and the actual behavior is very strange. Here’s what happens

  1. Once I create the realm instance, I observe that the data is being synced in the console, however, the collection changed event is not fired. I wait until the data is fully synced
  2. I press “N” inside the console, which executes the code inside the while loop
  3. The first 2 lines inside the while loop try to print the planets variable, which is still empty
  4. As soon as I create a new instance of realm using var newRealm = CreateRealm(app, realmDirectory); the CollectionChanged event is invoked and the full list of planets is present inside the original planets variable

Essentially, creating yet another realm instance acts as a trigger for the original realm to fire the CollectionChanged event.

This behavior introduces discrepancy between our realm MAUI application and the end to end test environment, and it doesn’t allow us executing E2E tests without changing the code that monitors for the collection changed events.

Why am I observing this discrepancy? Is there a way to guarantee change events in console/unit test environments?

Hi @Gagik_Kyurkchyan,

Actually xUnit should have a default synchronization context attached to all its tests, so can you please try to run the test without AsyncContext ? To be honest at the moment I am not sure about what is happening here, but it could be it is some peculiarity of xUnit.
In our code base we mostly run tests with NUnit in which we do need to use AsyncContext for the lack of a synchronization context. We do have a couple of smoke tests for xUnit though, just to verify the fundamentals, in which we don’t use AsyncContext

Hey @papafe

Thanks for getting back.

Actually, this demo was a trimmed-down example of the real application, and the issue wasn’t the realm. The issue was that I was blocking the thread on which I should have gotten the notifications because of another operation.

After resolving the issue, this issue was also resolved.

Thanks again.