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.