Optimize your UWP app without laziness

Optimize your UWP app without laziness

UWP apps are powerful, but due to adaptive design and the many form factors to support, your pages can be a lot more complex than with Windows phone for instance.

Let’s take an example, I recently release 6tin as a UWP app, it supports 5 different UI to adapt the user experience to many factors: device family, orientation, theme, user interaction (mouse/touch/gamepad) and of course screen size and density.

Here is an example of 2 different states:

00

In blue, controls specific to an UI.

01

Except the central part, all other controls aren’t used other UI, almost 50% of the visible view and 80% of the complexity is not used by some visual states

So imagine if I load ALL these controls on my phone, including controls specific to big screens, the app will takes a lot of time and RAM to load for nothing…

How I can fix it?

Visibility is not a solution

With XAML, we have 2 different states for visibility: Visible and Collapsed, but whatever the value, the item will be loaded, added to the visual tree and will use RAM. So how can I prevent for example the ad for big screens to load on phone?

2 solutions:

  • create these controls on code-behind
  • have a way to prevent the system to load some parts of our UI

The first solution is interesting, but not a good solution in production, your code will be hard to read.

So can I have a way to prevent the loading?

Yes, thanks to a new property brought by Windows 10:

x:DeferLoadStrategy=”Lazy

This property will prevent the system to parse a part of the xaml and load it if the visibility property equals Collapsed, for example:

<MediaElement x:Name=”Player” x:DeferLoadStrategy=”Lazy” Visibility=”Collapsed” />

The system will not load the MediaElement except if you change the visibility property, to summarize, the control will not exist, this.Player will be equal to null, no RAM will be used, etc…

Moreover, if you use DeferLoadStrategy on a Panel, the Panel as well as all its descendants will not be loaded/parsed/etc..

This is what I did on 6tin, the left panel is “Lazy” and Collapsed, so if I display this page on a phone, no CPU/GPU will be used to load/render all these useless controls, my page will use less memory and will be rendered faster.

So, how can I display them if they don’t exist?

doctor-who-spoilers-silence-431x300

I said the control doesn’t exist, but there is 2 ways to activate it.

Imagine we have a Grid named DesktopMenu:

<Grid x:Name=”DesktopMenu” x:DeferLoadStrategy=”Lazy” Visibility=”Collapsed”>

…. a lot of descendants

</Grid>

On code-behind this.DesktopMenu will be equals to null, so impossible to write: this.DesktopMenu.Visibility=Visibility.Visible

Visual States

The easiest solution is to use visual states, even if this.DesktopMenu == null, setters and storyboards will have access to the item.


<VisualState x:Name="WideState">
<VisualState.Setters>
<Setter Target="DesktopMenu.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>

Important fact: once the item visible, the property this.DesktopMenu will not be null anymore!

FindName

An other solution is to use FindName:


var myGrid=(Grid)myPage.FindName("DesktopMenu")

myGrid.Visibility=Visibility.Visible;

In fact, it’s exactly what the visual state do.

Best practices

Code-behind

Because of the Lazy system, an item can be null, so never write something like:


DesktopPanel.Width=200;

If the visibility has never be changed, this.DesktopPanel will be null and your application will crash, the best practice is to write:


(DesktopPanel??FindName("DesktopPanel")).Width=200;

Be careful will this usage, as soon as you use FindName, the object will be loaded as well as all its descendants. So for example, never writes something like:


if(DesktopPanel??FindName("DesktopPanel")).Visibility==Visibility.Collapsed)

If DesktopPanel equals null, FindName will be called and DesktopPanel (and its descendants) will be loaded for nothing. Instead write:


if(DesktopPanel==null || DesktopPanel.Visibility==Visibility.Collapsed)

Optimize all screens

Take advantage of the Lazy system and use it on ALL controls not used by ALL visual states. If a control/panel is collapsed on 1 or many visual states, set  x:DeferLoadStrategy=”Lazy” Visibility=”Collapsed” and change its visibility on visual states.

Never write something like:

<Grid>
<Grid x:Name="SmallMenu">
.... UI specific to small screens
</Grid>
<Grid x:Name="DesktopMenu" x:DeferLoadStrategy="Lazy" Visibility="Collapsed">
.... UI specific to wide screens
</Grid>
</Grid>

 

<VisualState x:Name="WideState">
<VisualState.Setters>
<Setter Target="DesktopMenu.Visibility" Value="Visible" />
<Setter Target="SmallMenu.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="NormalState"/>

This UI is optimized for small screens but not for wide screens, if you start your app on a wide screen, the SmallMenu will be loaded then collapsed, this step is totally useless and take ressources.

The default state must not be related to a specific UI, it must be the greatest common divisor of all your UI, only items used by ALL states must be visible, others must be collapsed and lazy.

If we change our previous example, DesktopMenu and SmallMenu must be collapsed and Lazy (cause not used on all our UI) and we will display them with visual states

<Grid>
<Grid x:Name="SmallMenu" x:DeferLoadStrategy="Lazy" Visibility="Collapsed">
.... UI specific to small screens
</Grid>
<Grid x:Name="DesktopMenu" x:DeferLoadStrategy="Lazy" Visibility="Collapsed">
.... UI specific to wide screens
</Grid>
</Grid>

 

<VisualState x:Name="WideState">
<VisualState.Setters>
<Setter Target="DesktopMenu.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="NormalState">
<VisualState.Setters>
<Setter Target="SmallMenu.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>

Conclusion

Don’t be lazy with lazy

Comments are closed.