1

I have an ObservableCollection bound to a ListView. This List is basically a selection of items. If you click one, its state changes. Its added to favourites and is shown in the favourites list(also a ListView) or it gets removed from favourites.

This is the main function of my app, so there will be a lot of adding-removing going on. Both lists are slow, buggy and flicker when updated.

How can I go about making it faster/smooth?

I have tried running all add/remove calls on a worker thread. I have tried using a Task and async function (which made it worse actually). Is there a "proper" implementation of this scenario?(ListView ui thread and worker thread keeping it up to date) Is there an article that could teach me the good practise?

Implementation: note: Truck is another class which stores the data. From Data class:

List<Truck> trucks = new List<Truck>();
public void addToFavorites(Truck truck)
{
    foreach(Truck t in trucks)
    {
        if(t == truck)
        {
            t.setFavorite(true);
        }
    }
}
public void removeFromFavorites(Truck truck)
{
    foreach (Truck t in trucks)
    {
        if (t == truck)
        {
            t.setFavorite(true);
        }
    }
}
public List<Truck> getTrucks()
{
    return trucks;
}
public List<Truck> getFavoriteTrucks()
{
    List<Truck> temp = new List<Truck>();
    foreach(Truck t in trucks)
    {
        if (t.isFavorite())
        {
            temp.Add(t);
        }
    }
    return temp;
}

From the page that shows all Trucks:

public partial class AllPage : ContentPage
{
    public AllPage(csharp.Data data)
    {
        //init
        InitializeComponent();
        this.data = data;
        //build list
        refreshAsync();
        ListView list = new ListView()
        {
            ItemTemplate = new DataTemplate(typeof(csharp.PlateCell)),
            ItemsSource = trucks,
            IsPullToRefreshEnabled = true,
        };
        //on select
        list.ItemSelected += (sender, e) => {
            if (e.SelectedItem == null) return; //row deselected, dont do anything
            var selection = e.SelectedItem as csharp.Truck;
            if (selection.isFavorite())
            {
                data.removeFromFavorites(selection);
                selection.setFavorite(false);
            }
            else { 
                data.addToFavorites(selection);
                selection.setFavorite(true);
            }
            ((ListView)sender).SelectedItem = null; // de-select the row
            refreshAsync();
        };
        list.RefreshCommand = new Command(() =>
        {
            //trucks = data.getFavoriteTrucks();
            refreshAsync();
            list.IsRefreshing = false;
        });
        //add the list to the page
        root.Children.Add(list);
    }//end constructor
    csharp.Data data;
    ObservableCollection<csharp.Truck> trucks = new ObservableCollection<csharp.Truck>();

    private async Task refreshAsync()
    {
        await Task.Run(() => refreshThread());
    }

    private void refreshThread()
    {
        List<csharp.Truck> all = data.getTrucks();
        trucks.Clear();
        foreach (csharp.Truck t in all)
        {
            trucks.Add(t);
        }
    }
}
Jamie Rees
  • 7,973
  • 2
  • 45
  • 83
fstam
  • 669
  • 4
  • 20
  • Could you add code how you implemented a _Its added to favorites and is shown in the favorites list(also a ListView) or it gets removed from favorites._ – Yehor Hromadskyi Aug 19 '16 at 11:27
  • Added the data class where everything is stored and the implementation of the ListView that shows this Data. Note that the Data class will in the future get its information from a server. The list is currently always the same for Data and allPage when it comes to the size and the objects it contains, this will not always be the case. – fstam Aug 19 '16 at 11:39
  • This has all that is needed - https://developer.xamarin.com/guides/xamarin-forms/user-interface/listview/performance/ – Rohit Vipin Mathews Aug 19 '16 at 15:04

2 Answers2

4

i think a good starting point is to inform yourself about Virtualizing

You should avoid updating your observable collection in other threads than the GUI thread (look at this)

A good point would also to have a look at MVVM

As far as i can see, you will always delete your list and fill it again, instead of propagating only the changes. -> this should be your focus in order to make use of virtualisation

also you might want to have a look at this. With the SmartCollection you will only fire the CollectionChanged Event once, instead for every item you add to your list

Community
  • 1
  • 1
Markus
  • 195
  • 1
  • 11
  • Added the implementation, will be reading through your links now. Thanks for answering! – fstam Aug 19 '16 at 11:41
  • As said, there might be more or less objects around everytime I refresh. At some point, definitly while doing a pull to refresh, I have to get the updated list from my data class. Also, when I change a field from a trucks object within my ObservableCollection, it does not update my listview. I will look through your links, Im sure Ill find my answer. – fstam Aug 19 '16 at 11:49
  • If an item gets updated inside the observableCollection it has to generate a PropertyChanged event, otherwise the changes will not update the UI. A good approach is to have all your viewmodels (in this instance the truck) inherit from a ViewModel baseclass that implements the [INotifyPropertyChanged](https://msdn.microsoft.com/de-de/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx) interface. The Set method of your property should than generate a PropertyChangedEvent for this property – Markus Aug 19 '16 at 11:56
  • Will look into that. Thanks again. – fstam Aug 19 '16 at 11:57
0

You might want to check out this thread talking about FastCell. I have not tried his library out yet but I am planning to as I also have a list which is very shaky on Android.

I have heard that limiting the amount of items in your list at any one time can help but it never seemed to do much for me.

Also, ListView now allows you to enable ListViewCachingStrategy. RecycleElement which is supposed to help but it sounds like for you it will not currently work because you are changing the contents of your ViewCell. I ran into the same thing. When enabled, iOS seems to cache your ViewCell's initial layout so when you change the ViewCell's layout, the UI does not update. I know there is a bug report for that somewhere but not sure what it's status is.

Besides that, I would make PlateCell as simple as you possibly can. The more images and labels and layouts in your ViewCell the worse it is going to be. You could also use AbsoluteLayout in your ViewCell which, if done right, can dramatically reduce the number of layout calculations needed.

The final step that I have not been willing to make yet is to write your ViewCell in native code which I hear is much more performant than writing one in Xamarin Forms.

Let us know if something does work out for you though!

hvaughan3
  • 10,955
  • 5
  • 56
  • 76
  • 1
    Implenting the INotifyPropertChanged interface as Markus suggested worked wonders. When I do a pull to refresh its stell messy though but Im willing to accept that, its only a second of mess. Making the listview smaller sounds like a good idea, or reducing the amount of items some other way, but you will run in to problems later. Not every device has the same screensize and unless all the listview items scale along, you will end up with the same problem again. As for the viewcell, you are probablt right. Setting hasUnevenRows to false helps alot with performance too. – fstam Aug 19 '16 at 12:59
  • @wasted Thanks for replying back with what worked for you. I had already implemented that but I am sure it will help someone else. If you find anymore tweaks let me know! Thanks again. – hvaughan3 Aug 19 '16 at 13:03
  • Rohit commented above, I found, in the article he posted, setting a listviews visibility or color after instantiating will cost performance. Check above, he posted a link with some more info that might be usefull to you. – fstam Aug 19 '16 at 16:21