Run Realm.All<object> from background thread


#1

I’m currently developing a Prism application which consumes a Realm database.Everything works pretty well, apart from a little detail: when I navigate from drawer navigator to some pages (say, Products - which contains about 2k items), the drawer animation slightly hangs. For the record, I’m calling _realm.All().SubscribeForNotifications from OnNavigatedTo method. I understand from where these hiccups comes from, since I’m synchronously updating my ObservableCollection from UI thread (and consequently blocking it), but my question is, there’s a better solution for this problem?

        public ProductsListPageViewModel(
            INavigationService navigationService,
            Realm realm
        )
            : base(navigationService)
        {
            Title = "Produtos";

            AllProducts = new ObservableCollection<Product>();
            Products = new ObservableRangeCollection<Product>();
            Products.CollectionChanged += (s, e) => RaisePropertyChanged(nameof(IsEmpty));

            _realm = realm;
        }

        public override void OnNavigatedTo(INavigationParameters parameters)
        {
            _realm.All<Product>().SubscribeForNotifications(UpdateProducts);
        }

        void UpdateProducts(IRealmCollection<Product> sender, ChangeSet changes, Exception error)
        {
            if (error != null)
            {
                return;
            }

            if (sender == null)
            {
                throw new ArgumentNullException(nameof(sender));
            }

            if (changes == null)
            {
                var items = sender.ToList()
                    .OrderBy(x => x.Description);

                AllProducts.Clear();

                foreach (var item in items)
                {
                    AllProducts.Add(item);
                }

                return;
            }

            for (int i = 0; i < changes.DeletedIndices?.Length; i++)
            {
                AllProducts.RemoveAt(changes.DeletedIndices[i]);
            }

            for (int i = 0; i < changes.InsertedIndices?.Length; i++)
            {
                var product = sender[changes.InsertedIndices[i]];
                AllProducts.Insert(changes.InsertedIndices[i], product);
            }

            for (int i = 0; i < changes.ModifiedIndices?.Length; i++)
            {
                var item = sender[changes.ModifiedIndices[i]];
                var model = AllProducts[changes.ModifiedIndices[i]];

                model.Description = item.Description;
                model.Unit = item.Unit;
                model.Category = item.Category;
            }

            for (int i = 0; i < changes.Moves?.Length; i++)
            {
                var move = changes.Moves[i];
                var temp = AllProducts[move.From];

                AllProducts[move.From] = AllProducts[move.To];
                AllProducts[move.To] = temp;
            }
        }


#2

One immediate problem is that you’re copying all items to a list for no apparent reason, then adding them to a 3rd collection (the case where changes are null, which is the initial notification being delivered). You’re effectively destroying the lazy loading benefits of using Realm, by copying all elements twice. Additionally, you’re sorting the collection in memory rather than sorting it at the database level, which will be much faster. Finally, is there a reason to use the observable collection and do all the proxying you’re doing as opposed to just use the result of the Realm query? It implements INotifyCollectionChanged, so you should be able to just databind to it.


#3

Ah, sorry! I’m essentially a web developer and have started working with Xamarin about 2 months ago, so, I’m still learning.

Initially, I should then use _realm.All().OrderByDescending(x => x.Description).SubscribeForNotifications(UpdateProducts) to order at database level, am I correct?

About the duplicate collection, in my application, the user may do some filtering, so, I’m basically filtering AllProducts and replacing Products collection:

void OnSearchCommandExecuted()
{
    if (string.IsNullOrEmpty(SearchText))
    {
        Products.ReplaceRange(AllProducts);
    }
    else
    {
        var filteredProducts = AllProducts.Where(FilterProductsByDescription);

        Products.ReplaceRange(filteredProducts);
    }
}

There will be more filtering options, but nothing much different from that. Could you suggest a better approach to do this filtering?

Thanks for taking the time to answer my question and for your kindness, like always.

P. S.: Another important thing to note: in the above example, AllProducts contains the raw Product model, but my actual ObservableCollection contains a Selectable class (where T is, for example, Product in this case). I removed some parts which I thought wouldn’t be so relevant for the question.


#4

No problem. Let’s leave the filtering for now as that will be slightly more involved :sweat_smile: Did you see any performance improvement by changing your code to do the ordering at the database level and then avoid .ToList-ing the results?


#5

Hey @nirinchev, I tried the suggested approach and, yes, I noticed some performance improvement, but I’m not sure the hiccup is related to Realm’s itself. I tried to navigate to the same page without loading any Realm data and it’s still hanging. Will need to do some investigation to try and discover whats effectively happening.


#6

Sounds good! Once you’re done investigating, if you still see Realm-related performance issues, let us know and we’ll take it from there.