When you think about a Xamarin (or any cross-platform) application, performance is the most important consideration. Optimizing performance in a native app is a bit easier than with a cross-platform application, as there are hundreds of blogs and books and other resources to help you along the way. But there are less of these resources when it comes to apps developed with cross-platforms like Xamarin.
This blog post intends to fill that gap. Let’s take a look at some of the best techniques from improving your Xamarin.Forms app performance.
Enable the XAML compiler:
XAML can be optionally compiled directly into intermediate language (IL) with the XAML compiler (XAMLC). XAMLC is disabled by default to ensure backward compatibility. However, it can be enabled at both the assembly and class level. For more information, see the official Compiling XAML documentation. Always enable XAML compilation if you’re using it:
Enable layout compression
Layout compression removes specified layouts from the visual tree in an attempt to improve page rendering performance. The performance benefit that this delivers varies depending on the complexity of a page, the version of the OS being used, and the device on which the application is running. However, the performance gains will actually be visible on older devices. For more information, see the official Layout Compression documentation:
Fast renderers reduce the inflation and rendering costs of Xamarin.Forms controls on Android by flattening the resulting native controls hierarchy. This further improves performance by creating fewer objects, which in turn results in a less complex visual tree and less memory usage.
Layout performance optimization
The most important aspect of layout-level optimization is knowing when you should be using which layout. As a XAML developer you should be aware of how each of these layouts work and what the drawbacks are of using each of them.
- A layout that’s capable of displaying multiple children, but that only has a single child, is wasteful. For example, a StackLayout with a single child does not make sense, as it’s just an additional burden over XAMLC.
- Planning on using StaticResources to reduce code redundancy and hardcode values? If yes, then you can save time by using styles to increase performance by not performing multiple lookups at the same time.
- In addition, don’t attempt to reproduce the appearance of a specific layout by using a combination of other layouts, as this results in unnecessary layout calculations being performed, and at the end of the day it doesn’t make much sense. For example, don’t attempt to reproduce a Grid layout by using a combination of StackLayouts.
- Don’t set the VerticalOptions and HorizontalOptions of a layout unless required. The default values for it, i.e.LayoutOptions.Fill and LayoutOptions.FillAndExpand, allow for the best layout optimization.
- Changing these properties has a cost and consumes memory, even when setting them to the default values. Hence, if your requirement is fulfilled using Fill/FillAndExpand, not mentioning it during the views creation is the best practice.
- Avoid using a RelativeLayout if at all possible. It has significant overhead and is never recommended.
- When using an AbsoluteLayout, avoid using the AbsoluteLayout.Autosize property whenever possible.
- Pack your views in the constructor rather than OnAppearing.
- Bypass transparency—if you can achieve the same (or close enough) effect with full opacity, do so.
- Use async/await to avoid blocking the main thread. And then show the end result using the Device.BeginInvokeOnMainThread();
- Inflate views off the main thread, but be sure to add it to the visual tree on the main thread. Failure to do so will not immediately crash your application, but will instead corrupt its state. Be particularly careful if you’re using MessagingCenter in your view’s constructor because it will not marshal the event to the correct thread for you.
- Reduce the depth of layout hierarchies by specifying Margin property values, allowing the creation of layouts with fewer wrapping views. For more information, check out the Margins and Padding documentation.
- When using a Grid, try to ensure that there as few rows and columns as possible that are set to Auto, which makes the engine perform additional calculations. Instead, use fixed-size rows and columns if possible.
- Set rows and columns to occupy a proportional amount of space with the GridUnitType.Star.
- When using a StackLayout, ensure that only one child is set to LayoutOptions.Expands. This property ensures that the specified child will occupy the largest space that the StackLayout can give to it. Note: It is wasteful to perform these calculations more than once.
- Don’t call any of the methods of the Layout class, as they result in the performance of expensive layout calculations. Instead, it’s likely that the desired layout can be obtained by setting the TranslationX and TranslationY properties. Alternatively, you can subclass the Layout<View> class to achieve the desired layout behavior.
- Avoid calling Layout() (and especially ForceLayout()).
- Update Label only when required, as changing the size of a label can result in the entire screen layout being recalculated.
- Don’t set a Label’s VerticalTextAlignment/HorizontalTextAlignment property unless required.
- Set the LineBreakMode of any label to NoWrap whenever viable.
- Including a ListView inside a ScrollView is a very bad practice. Always avoid it. Use the ListView‘s Header and Footer properties instead.
- Do not use TableView where you can use a ListView. TableViews are usually recommended for a setting like UI.
- Use ListViewCachingStrategy.RecycleElement when you can. This is not the default caching strategy.
- Use DataTemplate selectors to facilitate heterogeneous views within a single ListView. Don’t override OnBindingContextChanged to update and achieve the same effect.
- Avoid passing IEnumerable<T> as a data source to ListViews. Instead, try to use IList<T>, because IEnumerable<T> collections don’t support random access.
- Nesting ListViews is a bad practice. Instead, use groups within a single ListView. Nesting is explicitly unsupported and will break your application.
- DO use HasUnevenRows where your ListView has rows of differing sizes. If the content of the cell is modified dynamically (perhaps after loading it from the database), be sure to call ForceUpdateSize() on the cell.
- Avoid deeply nested layout hierarchies. Use AbsoluteLayout or Grid to help reduce nesting.
- Avoid specific LayoutOptions other than Fill (Fill is the cheapest to compute).
- Always await the PushAsync and PopAsync methods. Failure to do so is detrimental to both performance and correctness.
- Avoid hiding/showing the navigation bar.
- Use the AppCompat backend for Android. This will improve both performance and the look and feel of the application.
- Images on Android do not down-sample. Always remember this as it’s one of the reasons your app reaches OOM.
- Set Image.IsOpaque to true if possible.
- Load images from Content instead of Resources.
- Avoid using the CarouselPage as much as possible; instead use a CarouselView within a ContentPage. The reason for this is that CarouselPage loads all the data at once, which hampers performance at extreme levels.
- Avoid using MessagingCenter unless absolutely necessary.
- Pass MessagingCenter either as a static or an instance method, not a closure/lambda expression.
Optimize Image Resources
Displaying image resources can greatly increase the app’s memory footprint. Therefore, they should only be created when required and should be released as soon as the application no longer requires them.
For example, if an application is displaying an image by reading its data from a stream, ensure that stream is created only when it’s required, and ensure that the stream is released when it’s no longer required. This can be achieved by creating the stream when the page is created, or when the Page.Appearing event fires, and then disposing of the stream when the Page.Disappearing event fires.
When downloading an image for display with the ImageSource.FromUri method, cache the downloaded image by ensuring that the UriImageSource.CachingEnabled property is set to true. For more information, see the Working with Images documentation.
For more information, check out the full docs on optimizing image resources:
Use the Custom Renderer Pattern
Most renderer classes expose the OnElementChanged method, which is called when a Xamarin.Forms custom control is created to render the corresponding native control. Custom renderer classes, in each platform-specific renderer class, then override this method to instantiate and customize the native control. The SetNativeControl method is used to instantiate the native control, and this method will also assign the control reference to the Control property.
However, in some circumstances, the OnElementChanged method can be called multiple times. Therefore, to prevent memory leaks that can negatively impact performance, care must be taken when instantiating a new native control.
Remove Unnecessary Bindings:
Don’t use bindings for content that can easily be set statically. There’s no advantage in creating bindings for data that doesn’t need to be bound because bindings are an additional overhead, especially compared to static variables.
For example, setting Button.Text = “Submit” has less overhead than binding Button.Text to a ViewModel string property with the value “Submit”.
This blog described and discussed techniques for increasing the performance of Xamarin.Forms applications. Collectively, these techniques can greatly reduce the amount of work being performed by a CPU, and the amount of memory consumed by an application.
If I’ve missed something, go ahead and add it in the comments. I’ll make sure to add any needed changes to the post. Also, if you find something incorrect in the blog, please go ahead and correct me in the comments.
Smash that clap button if you liked this post.