Posted in: Development and Technology
Tags:

This is the 3rd part in my series on applying advanced techniques to Xamarin Forms to facilitate performant and flexible cross platform development. The previous parts discussed the role of Forms and how to improve list rendering performance. This time we cast our focus to GridView performance, looking at the XLabs gridView control.

GridViews, the concrete of mobile app construction.

As a mobile developer, the first thing I looked for when I came to Xamarin’s Forms Platform was a grid view. Grid Views, have become as fundamental as table and list views since their arrival in iOS 5 and Android 2 (that’s a guess, I’m an iOS guy – please comment if my vagueness irks you and I’ll fix it ), perhaps even more so. Why are grid views such a big deal?

The days of iOS 1, 2, and 3 with apps which are strung together as nested stacks of tables are ancient history. Users now expect to see all manner of data elements in all manner of layouts, with all manner of transitions. They also expect them to scroll by them slickly and smoothly. This is exactly the role of the grid view.

The first thing I found upon checking out Xamarin Form’s components, was that GridView wasn’t present. At the time this seemed quite odd. Since then, however, I’ve gotten a better idea of what Forms is and what it is not, and I can partially understand the absence. However, this was a great concern to me as our project (in fact most my iOS projects, for that matter) makes heavy use of GridViews. I must admit, I balked at the prospect of having to write my own. I was so new to Xamarin Forms that I realized it would be a huge undertaking for me at that point, demanding far more knowledge of the platform than I could hope to have, for at least several iterations of our project.

XLabs The Potion shop of Xamarin Forms.

Xamarin Labs (known as XLabs) is an open source project which acts as a repository of controls and application support classes for Xamarin Forms developers. If you have a control which you desperately need for your app, chances are its got you covered.

More than this though, XLabs has classes which clearly demonstrate patterns and idioms which all Xamarin developers would greatly benefit from.  Strategies for IOC, ViewFactories, and MVVM base classes, to name just a few. It’s a grab bag of anything an intrepid adventurer setting off into the unknown realm of Xamarin Forms development might want.

The project comes as a nuget, but it is open source. I installed the nuget (I believe it was the first one I ever installed – the memories!), and set to get developing my super cool GridViews.

Level 1 Boss hands my backside to me on a plate!

So, I got to work trying out the GridView…but instantly hit a slew of problems: 

  • I had to go through all the pain points of working out Xaml namespacing for the controls (no intellisense, no compiler time warnings)
  • Flicking between my code and the GithHb repo to view the XLabs code was really painful (I’d love to know how to get source code to show up along-side nugets; please comment if you know how)
  • I then found APIs which I was depending on to be missing (particularly horizontal layout). 
  • Performance was dreadful. Slow and Jerky for even the most simple of GridView.
  • I realized quickly that the off the shelf nuget wasn’t going to work for me, and I set about incorporating XLabs into my solution as additional projects (i.e. building it along-side my app).
  • This proved to be a crash-course in Xamarin Studio compiler error misery: as a Xamarin forms noobie on a mac, I didn’t know anything about Visual Studio project files, common terms (to assist me asking questions or googling), or nuances (of nuget, visual studio projects, or most impactingly, Xamarin Studio). It took me a stressfully long amount of time to get it building; but I’m glad I did, as I learned a ton along the way.

Using XLabs to help grok how renderers work.

The idea of renderers in Xamarin Forms is very straight-forward. It’s basically presenter pattern, with different presenters. It’d be familiar to anyone whose seen the MVVMCross project or Google Polymer (or web components, to which Google Polymer is a bridge), or angular-js directives, etc. However, understanding the theory of a pattern, and reading the documentation is one thing. Really “getting it” is another. And in my experience, the only way to really “get it” is to see it in action, and do it yourself.

Generally in Xamarin Forms, the first kind of renderers you build are pretty simple. I’ve spoken to others who found that the idea of going from such baby-steps, simple renderers to writing full-class support for existing native controls, especially ones as complex as GridView extremely daunting:

  • This is why I encourage everyone new to Xamarin Forms to not only use XLabs project, but to use it as source. 
  • That way when you get one of those ugly crashes, the debugger dumps you straight in the source code.
  • If there’s some small feature you need to add, you can add it easily, while furthering your knowledge of how renderers work.
  • You can spot opportunities to apply your expert knowledge of your platform (mine is iOS), to make improvements.
  • None of this would be possible with the nuget alone.

Importing XLabs source into your project.

I shan’t go into exhaustive detail about this, because I’ve extracted the GridView class into a smaller, more manageable project for you to either import into your own projects, or from which to copy the relevant files.

However, some of you may be curious about how I did it.

Create a Git subtree (because as you know, submodules are bad),

1. Import the following projects – I placed mine in a solution folder to make life more manageable:

Xlabs.Core,
Xlabs.Forms,
Xlabs.Forms.Droid,
Xlabs.Forms.iOS,
Xlabs.Ioc,
XLabs.IOC.XXXXXX your choice of IOC engine (for me it’s SImpleInjector),
Xlabs.Platform,
Xlabs.Platform.Droid,
Xlabs.Platform.iOS,
Xlabs.Serialization
2. For each project, ensure that the package references are set. You may find you need to right click packages, restore, then select the package, delete it, and add it again (I use Xamarin Studio on Mac, and this is often the only thing I can do to get packages to work – even when they restore, they don’t actually set the project references correctly),
3. I then set about compiling the projects one by one looking for build errors. These were almost always issues with the CsProject itself needing to be edited to remove certain items – typically the EnsureNuGetPackageBuildImports elements,  I also had to make other csProject file edits; but don’t remember them now – when you get those errors though, Google will will lead you to the solution.
4. I think I had to check all the project imports for all of the Xlabs projects.
5. Once I verified that the Xlabs solution folder built, I went ahead and edited the project references for all my projects, ensuring that relevant Xlabs projects were linked.
6. I then had XLabs source debugging and editing enabled.

Adding new properties.

At the time I edited the GridView, I must confess that Xamarin’s properties were an enigma to me. If it wasn’t for the kind generosity of Adam Kemp on the forum, I would’ve taken far longer still to understand it (I actually raised a bug, and believe Xamarin have since improved their documentation about this subject). However, also having access to so many rich example of components was a huge help in my understanding.

I started out by simply copy/pasting new properties to facilitate some extra padding I needed, and horizontal gridviews.

Adding better performance.

This was my first soiree into Xamarin Forms performance optimization. I’m a long term iOS developer, and a pretty good one at that. Once I understood how the GridView was implemented I was quickly convinced that performance was nowhere near what it should be. 

With source code access, I could now start to put breakpoints and logging in key methods, to get an idea of what was happening. The problems quickly became clear. They’re here in the GetCell method of GridViewRenderer (in the XLabs.Forms.iOS project):

public UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)  
            {  
                 var item = Element.ItemsSource.Cast<object> ().ElementAt (indexPath.Row);  
                 var viewCellBinded = (Element.ItemTemplate.CreateContent () as ViewCell);  
                 if (viewCellBinded != null)  
                 {  
                      viewCellBinded.BindingContext = item;  
                      return GetCell (collectionView, viewCellBinded, indexPath);  
                 }  
                 return null;  
            }  

 There’s a couple of weird things here for an iOS guy to start with, which is the null return, which equals total crash – I’ve never been sure why that’s there; but that aside – the inner call to GetCell just returns a UIViewCollectionCell populated with the viewCellBinded view.

The changes were very simple. This class is basically not implementing its flyweight properly. As you’ll recall, the flyweight pattern “minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory.” The dequeue method on the GridViewCollectionView is a flyweight implementation (it tracks reuse internal and allow us to swap out views to prevent unnecessary view creation).

UICollectionView actually has a very good flyweight implementation, as it goes. The one I’ve used by XLabs, unfortunately, is not. The GetCell method will create a ViewCell item for each and every invocation of this method. This is actually even worse than Xamarin’s ListView cells, which create a viewCell for every element in the Listview’s Item Source (so O(numberOfItemsInDataProvider)). This method here has a big O of number of times a cell is recycled. This is the worst performance case possible.

So getting native performance was just a case of creating and reusing the same amount of (Xamarin) ViewCells as native UICollectionViewCells. Being the SRP guy I am, I encapsulate the logic for cell-reuse in the GridViewCell class, so the GetCell method ends up looking like this:

  public UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)  
            {  
                 cellId = cellId ?? new NSString (GridViewCell.Key);  
                 var item = Element.ItemsSource.Cast<object> ().ElementAt (indexPath.Row);  
                 var collectionCell = collectionView.DequeueReusableCell (cellId, indexPath) as GridViewCell;  
                 collectionCell.RecycleCell (item, Element.ItemTemplate, Element);  
                 return collectionCell;  
            }  
And the GridViewCell class ends up like this:
  public class GridViewCell : UICollectionViewCell  
       {  
            UIView _view;  
            object _originalBindingContext;  
            FastGridCell _viewCell;  
            public void RecycleCell (object data, DataTemplate dataTemplate, VisualElement parent)  
            {  
                 if (_viewCell == null) {  
                      _viewCell = (dataTemplate.CreateContent () as FastGridCell);  
                      _viewCell.BindingContext = data;  
                      _viewCell.Parent = parent;  
                      _viewCell.PrepareCell ();  
                      _originalBindingContext = _viewCell.BindingContext;  
                      var renderer = RendererFactory.GetRenderer (_viewCell.View);  
                      _view = renderer.NativeView;  
                      _view.AutoresizingMask = UIViewAutoresizing.All;  
                      _view.ContentMode = UIViewContentMode.ScaleToFill;  
                      ContentView.AddSubview (_view);  
                      return;  
                 } else if (data == _originalBindingContext) {  
                      _viewCell.BindingContext = _originalBindingContext;  
                 } else {  
                      _viewCell.BindingContext = data;  
                 }  
            }  
            /// <summary>  
            /// The key  
            /// </summary>  
            public const string Key = "GridViewCell";  
            /// <summary>  
            /// Initializes a new instance of the <see cref="GridViewCell"/> class.  
            /// </summary>  
            /// <param name="frame">The frame.</param>  
            [Export ("initWithFrame:")]  
            public GridViewCell (CGRect frame) : base (frame)  
            {  
                 // SelectedBackgroundView = new GridItemSelectedViewOverlay (frame);  
                 // this.BringSubviewToFront (SelectedBackgroundView);  
                 BackgroundColor = UIColor.Black;  
            }  
            CGSize _lastSize;  
            public override void LayoutSubviews ()  
            {  
                 base.LayoutSubviews ();  
                 if (_lastSize.Equals (CGSize.Empty) || !_lastSize.Equals (Frame.Size)) {  
                      _viewCell.View.Layout (Frame.ToRectangle ());  
                      _viewCell.OnSizeChanged (new Xamarin.Forms.Size (Frame.Size.Width, Frame.Size.Height));  
                      _lastSize = Frame.Size;  
                 }  
                 _view.Frame = ContentView.Bounds;  
            }  
       }  

If you are observant, (and especially if you’re looking at the original GridViewCell class either on GitHub, or in your own project with XLabs included as sub-projects) you will notice that I do not use the ViewCell class;  instead, I use my own subclass, FastGridCell. This class looks like this:

  public abstract class FastGridCell : ViewCell  
      {  
           public bool IsInitialized {  
                get;  
                private set;  
            }  
            //          public Layout<Xamarin.Forms.View> Content { get; set; }  
            /// <summary>  
            /// Initializes the cell.  
            /// </summary>  
            public void PrepareCell ()  
            {  
                 InitializeCell ();  
                 if (BindingContext != null) {  
                      SetupCell (false);  
                 }  
                 IsInitialized = true;  
            }  
            protected override void OnBindingContextChanged ()  
            {  
                 base.OnBindingContextChanged ();  
                 if (IsInitialized) {  
                      SetupCell (true);  
                 }  
            }  
            /// <summary>  
            /// Setups the cell. You should call InitializeComponent in here  
            /// </summary>  
            protected abstract void InitializeCell ();  
            /// <summary>  
            /// Do your cell setup using the binding context in here.  
            /// </summary>  
            /// <param name="isRecycled">If set to <c>true</c> is recycled.</param>  
            protected abstract void SetupCell (bool isRecycled);  
            /// <summary>  
            /// Called when the size of the view changes. Override to do layout task if required  
            /// </summary>  
            /// <param name="size">Size.</param>  
            public virtual void OnSizeChanged (Size size)  
            {  
            }  
       }  

It is an abstract class which uses the strategy pattern to allow subclasses to know when to prepare themselves. We do this for a very good reason. A lot of ViewCell classes do one of these two things in their constructors:

  • If they are Xaml based they call InitializeComponent(), which will parse all the xaml and create views, an extremely expensive operation.
  • Otherwise create all the view controls (from C# code), which is still not cheap.

The Android implementations (more on that in another post), unfortunately require us to create a ViewCell for each list element. I am able to make huge performance gains by moving the expensive operations out of the constructor, and into a strategy which I can call when I need to (for example, if I know that a ViewCell will actually end up on screen, and therefore needs its children created.

Adding even more sugar – automatically spreading out cells with equal paddings.

I started to get bored of setting padding on my gridview in all of my pages, every time that the device was rotated. I decided to just fix this at the GridView component level. The solution is easy. I added a setting to allow one to elect to Center the content in a grid view (default is true). When this is enabled the GridView does some calculations to set correct content padding to ensure everything lays out nicely.

It looks like this:

  void UpdatePadding ()  
            {  
                 if (Element == null || (ICollection)Element.ItemsSource == null) {  
                      return;  
                 }  
                 var numberOfItems = ((ICollection)Element.ItemsSource).Count;  
                 UICollectionViewFlowLayout flowLayout = (UICollectionViewFlowLayout)_gridCollectionView?.CollectionViewLayout;  
                 if (flowLayout != null) {  
                      if (Element.IsContentCentered && numberOfItems > 0 && _gridCollectionView?.Frame.Width > 0) {  
                           flowLayout.InvalidateLayout ();  
                           float width = (float)_gridCollectionView.Frame.Width - 2;  
                           int numberOfItemsThatFit = (int)Math.Floor (width / (_gridCollectionView.ItemSize.Width));  
                           int numberOfItemsToUse = Element.CenterAsFilledRow ? numberOfItemsThatFit : (int)Math.Min (numberOfItemsThatFit, numberOfItems);  
                           var remainingWidth = width - (numberOfItemsToUse * (_gridCollectionView.ItemSize.Width));  
                           var padding = remainingWidth / (numberOfItemsToUse + 1);  
                           Console.WriteLine (" width {0} items using {1} padding {2} iwdith {3} ", _gridCollectionView?.Frame.Width, numberOfItemsToUse, padding, _gridCollectionView.ItemSize.Width);  
                           _gridCollectionView.ColumnSpacing = padding;  
                           _edgeInsets = new UIEdgeInsets ((float)Element.ContentPaddingTop, (float)padding, (float)Element.ContentPaddingBottom, (float)padding);  
                           Console.WriteLine ("final insets " + _edgeInsets);  
                           _gridCollectionView.ContentInset = _edgeInsets;  
                      }  
                      flowLayout.SectionInset = new UIEdgeInsets ((float)Element.SectionPaddingTop, 0, (float)Element.SectionPaddingBottom, 0);  
                      if (_gridCollectionView?.Frame.Width > 0 && _gridCollectionView?.Frame.Height > 0) {  
                           IsPaddingInvalid = false;  
                      }  
                 }  
            }  

It gets called from the LayoutSubView method. Note how I check that the size didn’t change – I wish Xamarin would insert some similar guard statements in their measurement invalidation code.

  public override void LayoutSubviews ()  
            {  
                 base.LayoutSubviews ();  
                 _gridCollectionView.Frame = this.Bounds;  
                 bool widthChanged = _previousWidth != _gridCollectionView.Frame.Width;  
                 if (widthChanged) {  
                      _previousWidth = _gridCollectionView.Frame.Width;  
                 }  
                 if (IsPaddingInvalid || widthChanged) {  
                      UpdatePadding ();  
                 }  
            }  

What’s performance like?

It’s pretty incredible actually – it’s so fast, that people who have seen our app (even from within Xamarin) find it hard to believe it’s written in Xamarin Forms. But it is.

 

Our sample app shows grid views with cells written in C#, in xaml (with 2 view stacks, images and labels) and even with nested GridViews (i.e horizontal grid views inside of vertical ones). In all cases performance is perfect.

 

How to use these improvements

You can head straight over to https://github.com/twintechs/TwinTechsFormsLib to download the TwinFormsLib Xamarin Forms library, which conveniently includes the GridView classes.

Samples are also included to show you how to get up and running. You can choose to copy all the relevant classes to your project, or simply use TwinFormsLib as a set of sub-projects (our recommendation is to pull it down as a git subtree).

The cells in the samples make use of our FastImage control, to facilitate the image caching. This does help smoothness considerably when it comes to images, but this is not where the majority of the performance boost comes from. If you want to get the same performance as we do, you’ll have to use both the FastImage and the GridView improvements we’ve made.

What about Android?

Those performance improvements are coming! The Android phase of our project starts in a week, and I will be revisiting this subject then. I already know the technique will work, as I saw the same performance increases using it for the ListView ViewCell renderers, as per my previous blog post.

[Update] I thought you might to see this is already coming along. Here’s a video showing vertical and horizontal scrolling – fast and smooth:

 

Where to go from here

At the time of writing I’ve only been using the Xamarin platform (and forms) for three months. I wasn’t happy to accept sub-standard performance, or missing features, and nor should you be.

I hope that inspires you to take a further look into open source projects for Xamarin and use them as spring-boards to deepen your knowledge. The architecture for Xamarin Forms allows us to get the very best performance and features from each platform. There’s a chance that you’re an expert in one of the platforms you’re targeting. If so, know that you can very easily resolve the issues you face. The community is full of smart people who are willing to help you work out a way to apply your expertise. Just make sure you share the results.

The last word must go to the XLabs team. I can’t thank them enough for providing me with the basis to so quickly become productive with Xamarin Forms. I am distributing their GridView code in our TwinForms library as that’s how I bundle it internally, and it’s perhaps a bit more friendly for less experienced developers to consume it like that. However, all credit must go to them for the work and I will be submitting pull requests with my optimizations shortly.

The source code from this series can be found in our TwinTechsForms library which you can download (it contains samples), or just cherry pick files from which to copy to your project.

Next time we will look at cross platform gestures in Xamarin Forms. Until then, happy hacking!

Oh, and please leave comments/questions if you have any further questions or need any advice get in touch.

Reach out to one of our experts