Responsive, Reactive, Adaptive design in MAUI

Why do we need to be responsive, reactive/adaptive?

Initially, when Apple introduced the first iPhone (and to be honest way before that with things like Windows CE) we had a fairly small and fixed sized device. Implementing applications for this, from a UI perspective, was fairly simple in that the constraints of the device size meant our applications wouldn’t need to dynamically change to different device sizes etc.

As time went by, we got tablets. Some applications weren’t rewritten for tablet and the UI would simply get scaled up and hence the UI looked like the phone UI just on a larger screen. In truth we can see the same issue with portrait and landscape modes. Some applications simply turn off the option to support landscape to force the user to stick with the UI design for portrait, some applications simply ignore the fact the screen has been rotated and use the same layout for portrait and landscape.

Now with OS’s supporting side by side docking, we can no longer just think of our device screen size as being static. Instead docking an application on something like my Samsung S6, where the application was designed for a tablet now needs to also take into account the potential of the application being docked.

Okay, that’s all a long-winded way of saying. To make truly cross platform UI and usable applications we need to think about our, previously static dimensioned, applications and resizable. So, basically just like on a desktop application.

What do we need our application to do?

Let’s look at some goals for our MAUI (or any framework) application to start to fulfill our needs. These are discussed in my previous post Introduction to Responsive, Reactive, Adaptive design but let’s now look into these concepts with a view to how we might implement such things.

  • Element styles – this may relate to any style used, but to be specific, let’s look at fonts as an example. We need to change our font sizes for the different sized devices. I mean a 24 pt font might look great on mobile but becomes a small island of text in a larger landscape of a tablet or desktop app.
  • Element Sizes – we need a way to change the sizes of elements to suit the size of the device. For example, a button with a fixed size for mobile will become lost on larger screens. Ofcourse MAUI, WPF etc. come with grid and other layouts that can help here.
  • Element Positioning – we need a way to move elements around the screen. Displaying buttons on the bottom of the screen in portrait mode may look great, but when switch to landscape, maybe they need to be repositioned to the side of the screen (for example).
  • Changing layouts – taking a mobile app that has two section, one page with a list with navigation items and another page that displays when click by the navigation items is great on mobile but for tablet or desktop, would be better if the navigation page become a docked panel on the left of the screen and clicking links shows the navigated page on the right of the screen – this is a pretty standard layout change you’ll see in lots of apps.

Note: This post is primarily aimed at MAUI, however the concepts etc. are valid for other UI frameworks and also are not limited to mobile devices. Desktop applications can also offer better user experiences if they can adapt to the size of the window displaying them.

Before looking into some code example etc. Create yourself a MAUI application (if you want to follow along) and we’re going to solely. I’m going to run on Windows and run the MAUI application as a Windows application, because I can easily resize it to see the effects of my code. Ofcourse if we get things working on a Windows desktop application, we should have no trouble with the same code on mobile devices (as long as we choose the right style options etc.).

Element Styles

This should be the simplest thing to implement. We’ve got a MainPage (in MAUI) with some text on it, so the code looks like this

<Grid>
   <Label 
      Text="My Application"
      VerticalOptions="Center" 
      HorizontalOptions="Center" />
</Grid>

Now we’ll stick to running this as a Windows application as this will allow us to dynamically resize our window/view. If you run your MAUI app the text will be an okay size in the middle of the screen, but we want to be able to change the label’s font size based upon the size of the window/device.

We know that we can use OnIdiom along with a resource to set the FontSize like this

Note: I’ve extracted only the code which changes, so obviously you’ll need to copy this code to the correct places if you’re following along.

<ContentPage.Resources>
    <OnIdiom x:Key="FontSize" x:TypeArguments="x:Double" 
      Default="24" Phone="48" Tablet="96" Desktop="128"/>
</ContentPage.Resources>

<Label 
   Text="My Application"
   VerticalOptions="Center" 
   HorizontalOptions="Center" 
   FontSize="{StaticResource FontSize}"/>

In the above, if you run the app. in Windows you’ll get the Desktop assigned FontSize (128) and on a phone (or phone emulator) the text will displayed using that assigned FontSize. However, as you’ll have noticed, this only partially fulfills our requirements. It does display with different font sizes but it’s static. If you dock an app side by side on a tablet with this, the FontSize remains the same – as you probably expect as this is solely checking what the device/idiom is.

However, we now see that we can use resources to change things based upon some device value. We just need a way to respond to our Window size dynamically.

Responding the page size changes

MAUI 6.x does not have anything that handles this sort of thing for us, there is the AdaptiveTrigger, but in the Maui 6.x releases this does not work, so we will look at it later and it may eventually be the best solution, but for now I’m on 6.x and hence it’s unusable, so let’s proceed with what we currently have available.

The simplest way to achieve this, for now, is to write some code in our page’s code behind. If we change our MainPage code to look like this

public MainPage()
{
   InitializeComponent();
   SizeChanged += OnSizeChanged;
}

private void OnSizeChanged(object sender, EventArgs e)
{
   // code for resizing goes here
}

Let’s remove the OnIdiom code and the FontSize from the label in our previous example and now add a name to the label so it looks like this

<Label 
   x:Name="MainLabel"
   Text="My Application"
   VerticalOptions="Center" 
   HorizontalOptions="Center" />

Now change our new OnSizeChange method to have the following

MainLabel.FontSize = Width > 400 ? 128 : 48;

This is basically what we want to achieve, but this isn’t very reusable by other components or properties, but it works. Let’s move a step further towards our goals and change things so we can have our XAML change values for us. To do this, we’re going to switch to using the Visual State Manager (VSM). Change the OnSizeChange code to the following

VisualStateManager.GoToState(MainLabel, Width > 400 ? "Large" : "Default");

At this point we’re effectively moving the logic for setting the sizes etc. into our XAML. This is a good step forward, but we are still (at this time) tied to the MainLabel element (and this is not good). Bare with me.

Let’s look at how we would use this change in our XAML – change the Label to look like this

<Label x:Name="MainLabel"
   Text="My Application"
   VerticalOptions="Center" 
   HorizontalOptions="Center">
   <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="Responsive">
         <VisualState x:Name="Large">
            <VisualState.Setters>
               <Setter Property="FontSize" Value="128" />
               <Setter Property="TextColor" Value="Green" />
            </VisualState.Setters>
         </VisualState>
         <VisualState x:Name="Default">
            <VisualState.Setters>
               <Setter Property="FontSize" Value="48" />
               <Setter Property="TextColor" Value="Red" />
            </VisualState.Setters>
         </VisualState>
     </VisualStateGroup>
  </VisualStateManager.VisualStateGroups>
</Label>

As previously stated, we are still bound to the x:Name visual element (which is not perfect) but we can now change any property in this named element, based upon the visual state as demonstrated by change FontSize and TextColor.

This approach also suffers a problem with multiple controls reusing the VisualState names such as x:Name=”Large”. There may be a way around this that I’ve not discovered yet.

Our end goal is for multiple controls changing based upon the VSM states. Let’s start by moving the VSM XAML to the ContentPage itself. This means we no longer care about the explicit element accepting the state, but it’s moved to the ContentPage and from there we reference the specific elements using TargetName

<VisualStateManager.VisualStateGroups>
   <VisualStateGroup x:Name="Responsive">
      <VisualState x:Name="Large">
         <VisualState.Setters>
            <Setter Property="BackgroundColor" Value="Blue"/>

            <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Green"/>
            <Setter TargetName="MainLabel" Property="Label.FontSize" Value="128"/>
         </VisualState.Setters>
      </VisualState>
      <VisualState x:Name="Default">
         <VisualState.Setters>
            <Setter Property="BackgroundColor" Value="Azure"/>

            <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Red"/>
            <Setter TargetName="MainLabel" Property="Label.FontSize" Value="48"/>

         </VisualState.Setters>
      </VisualState>
   </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<Grid>
   <Label x:Name="MainLabel"
      Text="My Application"
      VerticalOptions="Center" 
      HorizontalOptions="Center" />
</Grid>

We’ll again need to change the code behind, but now just need to send state changes to the page itself, i.e.

VisualStateManager.GoToState(this, Width > 400 ? "Large" : "Default");

As mentioned, what we’ve done is gained the ability to change, not only the label FontSize and TextColor but also the BackgroundColor of the Page and potentially anything else displayed within the page. We’ve also removed any knowledge of the controls displayed on the page from the code behind (i.e. removed the name MainLabel from the code behind).

One more issue we might want to look into is that we are currently coding the different breakpoint size i.e. Width > 400 into the code behind. It would be better if we could move this to the XAML or some other mechanism not requiring us to edit the code behind at all.

AdaptiveTrigger

One way of removing the VisualStateManager code and the handling of the SizeChanged event is to use the AdaptiveTrigger.

At the time of writing (6.x MAUI) this doesn’t work correctly, it appears to have been fixed in 7.x, so for now we cannot use the AdaptiveTrigger, but let’s take a look at the code changes that should work when it’s fixed.

We’d remove all the code we added to the page’s code behind and our XAML would look something like this

<VisualStateManager.VisualStateGroups>
   <VisualStateGroup x:Name="Responsive">
      <VisualState x:Name="Large">
         <VisualState.StateTriggers>
            <AdaptiveTrigger MinWindowWidth="1200" />
         </VisualState.StateTriggers>
         <VisualState.Setters>
            <Setter Property="BackgroundColor" Value="Blue"/>

            <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Green"/>
            <Setter TargetName="MainLabel" Property="Label.FontSize" Value="128"/>
         </VisualState.Setters>
         </VisualState>
      <VisualState x:Name="Default">
         <VisualState.StateTriggers>
            <AdaptiveTrigger MinWindowWidth="0" />
         </VisualState.StateTriggers>
         <VisualState.Setters>
            <Setter Property="BackgroundColor" Value="Azure"/>

            <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Red"/>
            <Setter TargetName="MainLabel" Property="Label.FontSize" Value="48"/>
         </VisualState.Setters>
      </VisualState>
   </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Lines such as <AdaptiveTrigger MinWindowWidth=”1200″ /> are effectively defining our UI breakpoints. We’ll discuss more around breakpoints in the next section.

Breakpoints

We’ve seen that whatever solution we try to use, we’ll need some way to define our breakpoints. The dimensions at which we change layouts etc. If we look to create breakpoints in line with bootstrap documentation, for example. Then we might declare some resources like this

<x:Double x:Key="ExtraSmall">0</x:Double>
<x:Double x:Key="Small">576</x:Double>
<x:Double x:Key="Medium">768</x:Double>
<x:Double x:Key="Large">992</x:Double>
<x:Double x:Key="ExtraLarge">1200</x:Double>
<x:Double x:Key="ExtraExtraLarge">1400</x:Double>

and use these in our AdaptiveTrigger’s.

Great, but what can we use now?

As stated several times, as of MAUI 6.x we cannot use AdaptiveTrigger’s, so what can we do which won’t end up requiring us to write code-behind etc.? Well, if we stick to using the VSM, then we need a way to attach to the ContentPage and a way to use the VSM to trigger our UI changes.

One way to achieve this is by creating a behavior for the ContentPage type something like this

public class BreakpointBehavior : Behavior<Page>
{
    protected override void OnAttachedTo(Page page)
    {
        page.SizeChanged += PageSizeChanged;
        base.OnAttachedTo(page);
    }

    protected override void OnDetachingFrom(Page page)
    {
        page.SizeChanged += PageSizeChanged;
        base.OnDetachingFrom(page);
    }

    private void PageSizeChanged(object sender, EventArgs e)
    {
        if (sender is Page page)
        {
            VisualStateManager.GoToState(page, ToState(page.Width));
        }
    }

    private string ToState(double width)
    {
        if (width >= 1400)
            return "ExtraExtraLarge";
        if (width >= 1200)
            return "ExtraLarge";
        if (width >= 992)
            return "Large";
        if (width >= 768)
            return "Medium";
        if (width >= 576)
            return "Small";

        return "ExtraSmall";
    }
}

and now our ContentPage would look like this

<ContentPage.Behaviors>
   <extensions:BreakpointBehavior />
</ContentPage.Behaviors>

<VisualStateManager.VisualStateGroups>
   <VisualStateGroup x:Name="Responsive">
      <VisualState x:Name="ExtraExtraLarge">
         <VisualState.Setters>
            <Setter Property="BackgroundColor" Value="Blue"/>

            <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Pink"/>
            <Setter TargetName="MainLabel" Property="Label.FontSize" Value="128"/>
         </VisualState.Setters>
      </VisualState>
      <VisualState x:Name="Large">
         <VisualState.Setters>
            <Setter Property="BackgroundColor" Value="Blue"/>

            <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Green"/>
            <Setter TargetName="MainLabel" Property="Label.FontSize" Value="96"/>
         </VisualState.Setters>
      </VisualState>
      <VisualState x:Name="Medium">
         <VisualState.Setters>
            <Setter Property="BackgroundColor" Value="Azure"/>

            <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Red"/>
            <Setter TargetName="MainLabel" Property="Label.FontSize" Value="48"/>

         </VisualState.Setters>
      </VisualState>
   </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<Grid>
   <Label x:Name="MainLabel"
      Text="My Application"
      VerticalOptions="Center" 
      HorizontalOptions="Center" />
</Grid>

Essentially, we add visual states to match what the BreakpointBehvior sets and change our UI accordingly. We might look to make additions to the behavior to allow us to set the breakpoint properties via our XAML that way, we can respond to custom defined breakpoints.

We’ve covered a lot of ground but only really looked at the possibilities for responsive design, i.e. we can change properties but we’re not changing the layout.
Also, this code does not handle portrait or landscape orientations.

Changing layouts altogether

Whilst we can change properties on our layouts using the example shown here. It really would be much simpler if we could simply change layouts altogether and design the different layouts required separately. This is definitely useful when looking at portrait/landscape changes.

For Xamarin Forms I had a simple way of handling this, it may not be the best way or efficient, but it allowed me to host different views in a ContentView in a very simple way.

See my post Handling orientation in Xamarin Forms for the code of OnOrientation. This code works with MAUI.

Our XAML might look something like this

<extensions:OnOrientation>
   <extensions:OnOrientation.Portrait>
      <VerticalStackLayout>
         <Label Text="My Application Portrait"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
      </VerticalStackLayout>
   </extensions:OnOrientation.Portrait>
   <extensions:OnOrientation.Landscape>
      <Grid>
         <Label Text="My Application Landscape"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
      </Grid>
   </extensions:OnOrientation.Landscape>
</extensions:OnOrientation>

So, what about an alternative to the code above?

Changing layouts using DataTemplates and DataTemplateSelector

MAUI, WPF etc. already has the ability to define alternate layouts templates. We can create DataTemplate resources, one for portrait, one for landacape and then use AdaptiveTriggers or VSM to be used based upon the device orientation. We can then define a DataTemplateSelector to simply switch in and out the template based upon orientation (a bit like my OnOrientation code, above).

Let’s forget about the device info telling us what the orientation of the device is, but instead we’ll simply define landscape ad having a Width great then the height otherwise it’s in portrait orientation.

We’re going to now start to put together some of the pieces we’ve already discussed…

Sorry, this is a bigger chunk of code, but our ContentPage should now look like the following

    <ContentPage.Resources>
        <DataTemplate x:Key="Portrait">
            <VerticalStackLayout>
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="Responsive">
                        <VisualState x:Name="Medium">
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="Blue"/>

                                <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Green"/>
                                <Setter TargetName="MainLabel" Property="Label.FontSize" Value="128"/>
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name="Small">
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="Azure"/>

                                <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Red"/>
                                <Setter TargetName="MainLabel" Property="Label.FontSize" Value="48"/>

                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>

                <Label x:Name="MainLabel"
                       Text="My Application Portrait"
                       VerticalOptions="Center" 
                       HorizontalOptions="Center" />
            </VerticalStackLayout>
        </DataTemplate>
        <DataTemplate x:Key="Landscape">
            <Grid>
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="Responsive">
                        <VisualState x:Name="ExtraExtraLarge">
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="Blue"/>

                                <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Green"/>
                                <Setter TargetName="MainLabel" Property="Label.FontSize" Value="128"/>
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name="Medium">
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="Azure"/>

                                <Setter TargetName="MainLabel" Property="Label.TextColor" Value="Red"/>
                                <Setter TargetName="MainLabel" Property="Label.FontSize" Value="48"/>

                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>

                <Label x:Name="MainLabel"
                       Text="My Application Landscape"
                       VerticalOptions="Center" 
                       HorizontalOptions="Center" />
            </Grid>
        </DataTemplate>
        <extensions:OrientationDataTemplateSelector x:Key="OrientedView"
            Landscape="{StaticResource Landscape}" 
            Portrait="{StaticResource Portrait}" />
    </ContentPage.Resources>

    <ContentPage.Behaviors>
        <extensions:AdaptableBehavior OrientationTemplateSelector="{StaticResource OrientedView}" />
    </ContentPage.Behaviors>

There’s a lot there, but hopefully it makes sense. We’re defining two data templates, the first for Portrait, the second for Landscape, we’re also defining state changes based upon some breakpoints, Medium and Small. We also define the actual UI/layout within each template.

Next, we’re using a DataTemplateSelector, that I’ve created, named OrientationDataTemplateSelector. This will simply choose the data template based upon the orientation (width against height) of the window/view. The code for this looks like this

public class OrientationDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate Landscape { get; set; }
    public DataTemplate Portrait { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        return item?.ToString() == "Portrait" ? Portrait : Landscape;
    } 
}

This is just an example, you might prefer to use enum’s for the value, for example.

The DataTemplateSelector is simply a way to say use this template for landscape and this one for portrait but we now need a way to tell the selector when to use each of these data templates. This is what I’ve created the AdaptableBehavior for. Here’s the code

public class AdaptableBehavior : Behavior<ContentPage>
{
    public static readonly BindableProperty OrientationTemplateSelectorProperty = BindableProperty.Create(nameof(OrientationTemplateSelector),
        typeof(DataTemplateSelector), typeof(OrientationLayout));

    public DataTemplateSelector OrientationTemplateSelector
    {
        get => (DataTemplateSelector)GetValue(OrientationTemplateSelectorProperty);
        set => SetValue(OrientationTemplateSelectorProperty, value);
    }

    private static View CreateItemView(object item, DataTemplate dataTemplate)
    {
        if (dataTemplate != null)
        {
            var view = (View)dataTemplate.CreateContent();
            view.BindingContext = item;
            return view;
        }

        return new Label { Text = item?.ToString(), HorizontalTextAlignment = TextAlignment.Center };
    }


    protected override void OnAttachedTo(ContentPage page)
    {
        page.SizeChanged += PageSizeChanged;
        base.OnAttachedTo(page);
    }

    protected override void OnDetachingFrom(ContentPage page)
    {
        page.SizeChanged += PageSizeChanged;
        base.OnDetachingFrom(page);
    }

    private void PageSizeChanged(object sender, EventArgs e)
    {
        if (sender is ContentPage page)
        {
            var orientation = GetOrientation(page);
            var dataTemplate = OrientationTemplateSelector;
            var selected = dataTemplate.SelectTemplate(orientation, page);
            page.Content = CreateItemView(orientation, selected);

            VisualStateManager.GoToState(page.Content, ToState(page.Width));
        }
    }

    private string GetOrientation(Page page)
    {
        return page.Width > page.Height ? "Landscape" : "Portrait";
    }

    private string ToState(double width)
    {
        if (width >= 1400)
            return "ExtraExtraLarge";
        if (width >= 1200)
            return "ExtraLarge";
        if (width >= 992)
            return "Large";
        if (width >= 768)
            return "Medium";
        if (width >= 576)
            return "Small";

        return "ExtraSmall";
    }
}

We can make the breakpoints settable via XAML or some other way to configure them, but you get the idea.

The AdaptableBehavior now does as our earlier example and responds to both size (breakpoints) as well as orientation. It uses the supplied DataTemplateSelector to choose the correct data template to use and then uses VSM to tell the template which size breakpoints to use.

If you run this example in Windows you’ll find it handles some different breakpoints but then when the breakpoint is not assigned any values the sizes will switch back to the defaults. As the AdaptableBehavior has no idea what’s listening to state changes, it’s difficult for it to handle this itself.

Ofcourse, there are other ways to achieve this, such as making the AdaptableBehavior the DataTemplateSelector (essentially like my Xamarin Forms OnOrientation code) and simply supply it with the various orientations and breakpoint UI templates. I’ll leave that to the reader to look into.

Is handling breakpoints using width good enough?

In much of this post we talk about breakpoints around the display’s width, but is just handling the MinWindowWidth good enough for our needs.

If we solely handle the width as our breakpoint trigger then we have a potential issue if the device switches to landscape mode. For example, let’s assume that we set the font size to 48 for small displays (upto 1200) and 96 for larger or equal to 1200 width. On a mobile phone in portrait the font may look perfect at 48, but when the phone is rotated to landscape mode the display is greater than 1200, switching to the larger font size which is now possible unusable.

We could obviously look at using triggers with breakpoints and then use orientation of device/platform to have alternate values or we could look to handle the height as a trigger as well – as you can see, this then starts to get complicated, requiring many visual states or triggers etc. for many device scenarios.

Ofcourse, we could fix the UI to portrait and make our lives simpler, but this will not suit all applications.

So, make sure you test your UI on different sizes devices and in different orientations.

There’s more…

Before we end this rather long post. I mentioned that the AdaptiveTrigger would (in MAUI 7.x) allow us to define XAML that’s triggered by breakpoint changes, there’s also the OrientationStateTrigger used like this

Update: I’ve just confirmed, MAUI 7.x has a working version of AdaptiveTrigger.

<VisualState.StateTriggers>
   <OrientationStateTrigger Orientation="Portrait" />
</VisualState.StateTriggers>

Is that it?

Our original aims were to be able to change things like font size, control sizes and layouts to be truly adaptive. Using DataTemplates with either triggers or behaviours allows us to achieve these goals, but there one last thing to look into/think about.

Not quite the same thing but MAUI controls come with the property FontAutoScalingEnabled set to true by default. Now this basically says, if the device scales the font, then our UI/control should respond accordingly and scale.

This is the option of some devices to change the Settings | Display | Font size. So when, like me, your eyesight’s not great on small devices, you tell the device to scale fonts to a larger size, FontAutoScalingEnabled=”True” means your font will get scaled, False means it will not. This is important to remember if you are already handling things like scaling your font size based upon breakpoints as it could affect your UI look if you max your font via the breakpoint and then find the user scales it further.

Let’s see this in actions. If we change our MainPage ContentPage to just having this XAML

<Grid>
   <Label Text="My AutoScaling Label"
      VerticalOptions="Center" 
      HorizontalOptions="Center"
      FontSize="32"
      FontAutoScalingEnabled="True" />
</Grid>

Now we run this up on an Android Phone emaulator, I’m using the Pixel 3 XL. What you should see is our text nicely display on a single line at the centre (vertically and horizontally) on the emulator. All looks pretty good.

Now go to the Android Settings | Display | Advanced section and select Font Size, you should see some sample text and a control to change the default text size – change this to the largest option, now return to your application UI and I was lucky, the label just fit on the one line, but as you can see, the font size increased and hence this might have an effect on your layout and design if you’ve already maximized your font size for the given screen size.

Hence, if you need to stop this happening, set FontAutoScalingEnabled=”False”

Code

Code is available via my blog-projects github repo..

Introduction to Responsive, Reactive, Adaptive design

Note: This is an introduction to concepts around Responsive, Reactive and Adaptive design, code will follow in subsequent posts aimed at implementing such designs in MAUI applications.

When we start developing cross platform applications with frameworks such as MAUI, Xamarin Forms, React Native etc., we really want to create (where possible) one code base for our application (only deviating from this for platform specific code).

As you’ll probably aware, the real problem areas for this are in the UI code, and this is really what this post is all about.

Definitions

I’ve based these definitions on various websites and blogs talking about such things. Personally, I tend to feel that they all ultimately mean the same thing which is just simply, how to make our apps work best on different devices, screen sizes and orientations. But, let’s take a look at the definitions anyway.

  • Responsive – A responsive design takes a single layout and changes elements on the page, such as adjusting font size, even adding elements to utilise the space or a device accordingly but the general layout remains unaltered.

    Note: Responsive also refers to Responsive UI, i.e. when a UI feels responsive, such as when a button is clicked how it gives feedback etc. and is as non-blocking as possible. This is a different subject altogether, so not covered here.

  • Reactive/Adaptive – A reactive (also known as adaptive) design will use different layouts to cater for different screen sizes and/or devices. The classic for this is a design for mobile screen sizes which has a single navigation page that navigates to other pages and back, whereas on a tablet or desktop, the navigation pages becomes a docked (usually to the left) page which the pages previously navigated to, shown on the right (as and when selected).

So, in essence, responsive designs change values within the existing design to respond to the device/space, whereas a reactive or adaptive (I’ll stick with using the word adaptive for reactive/adaptive design from now on) design has specific designs for each device/size of view.

What we’re really talking about is how to make our application look good and work well on different screen sizes and orientations.

If our application design can get away with just changing font sizes, then that’s a relatively easy win. However, to truly cover all options we need to look at how our application might be more adaptive.

Breakpoints

Web technologies, whether CSS or other has been implementing adaptive designs for a while. A website may be designed for mobile-first where it has a single page with a hamburger menu or other means of navigating to other pages but when that same view is expanded to a larger size, the UI may switch to show a navigation bar on the left and each page that’s navigated to gets displayed on the right. This is a pretty standard sort of design, UWP templates use it and I’ve used for my My Unit Conversion app to show different layouts on phone and tablet as well as different orientations. Anyway, back to breakpoints…

The word Breakpoints is used to refer to the screen sizes that we might want our application to adapt to. So, typically we’ll have a mobile device (or smaller still a watch), tablet and ofcourse a desktop and maybe even a large screen TV. We can think of these screen sizes as different breakpoints, in other words the points at which the app. or website changes to provide the best UI and UX.

For example, if we take a look at the boostrap breakpoints, by default the breakpoints are separated into x-small, small, medium, large, extra large and extra extra large. As you can see, there’s no mention of devices like mobile phone or tablet, instead we just need to concern ourselves with the screen or more specifically window/view sizes. With such a system we can change the size and shape of a window/view on a desktop and it’ll change layouts etc. based upon the breakpoints being reached.

So what do we need to support for a fully adaptive application?

  • Fonts – we need a way to expand text to better fill and display on larger devices. I mean a 24 pt font might look great on mobile but becomes a small island of text in a larger landscape of a tablet or desktop app.
  • Element Sizes – closely related to font sizes. A button that says OK will become lost on larger screens.
  • Element Positioning – we need a way to move elements on the screen. For example, maybe in landscape orientiation it makes sense to put buttons on the right of the screen instead of the bottom.
  • Changing layouts – (the canonical example discussed above also). Take a mobile app that has two section, one page with a list with navigation items and another page that displays when click by the navigation items is great on mobile but for tablet or desktop, would be better if the navigation page became a docked panel on the left of the screen and clicking links shows the navigated page on the right of the screen – this is a pretty standard layout change you’ll see in lots of apps.

What’s next?

This post just covers some of the concepts around creating adaptable UI’s, in the next post we’ll look at using this knowledge to create more MAUI applications.

Useful references

Screen sizes
Breakpoints
Adaptive Design vs. Responsive Design

Change the statusbar using MAUI

The status bar is the top most bar which shows things such as the battery, wifi etc. indicators. Now I got a little way to achieving this but then hit a snag – so shout out to Change Status Bar Color for Android and iOS in .NET MAUI where Gerald Versluis demonstrates the MAUI Cummunity Toolkit behaviour for solving this problem.

First off install the CommunityToolkit.Maui Nuget package 1.3.0 or above.

In MauiProgram.cs add the following to the builder.

.UseMauiCommunityToolkit();

In the MainPage.xaml we can set up our behaviour like this

<!-- You'll need the following -->
xmlns:behaviors="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"

<!-- Then within the ContentPage put the following -->
<ContentPage.Behaviors>
   <behaviors:StatusBarBehavior StatusBarColor="#FF013558" StatusBarStyle="LightContent" />
</ContentPage.Behaviors>

The statusbar colour is whatever you’re setting for you apps. base colour. The status bar style refers to the text/icons. Obviously for a dark theme/colour, such as above, you’ll want to set the style to light content. If you go for a lighter background/theme you’ll tend to set the style to DarkContent.

These fields are ofcourse bindable, so you can change as you wish.

And that’s it!

Wait, before we end this discussion, you’ll notices things don’t quite work for iOS. We do not see the colour changes to the status bar. In this case we need to edit the info.plist (best to use an XML/text editor as the option is not supported in the Visual Studio UI for this) and add

<key>UIViewControllerBasedStatusBarAppearance</key>
<false />

useState and useEffect in React

Since React’s move to using hooks instead of classes, two of the primary hooks we need to get used to using are useState and useEffect.

Note: I’m using Typescript for my React apps. so code listed will have types etc. but if you’re from a pure Javascript background it should still be pretty obvious what’s going on.

useState

So, in the time before hooks, we might use classes. We’d pass props into the constructor of the class and set state within the class. Hence, we might end up with something like this

interface IViewerProps {
   startAt: number;
}

interface IViewerState {
    counter: number;
}

export class Viewer extends React.Component<IViewerProps, IViewerState> {
   constructor(props: IViewerProps) {
      super(props);
         this.state = {
            counter:  props.startAt
         }

      this.onClick = this.onClick.bind(this);
   }

   onClick(): void {
      this.setState({
         counter: this.state.counter + 1
      });
   }

   render() {
      return (
         <div>
            <div>{this.state.counter}</div>
            <button onClick={this.onClick}>Click Me</button>
         </div>
      );
   };
}

In this instance we’d pass a startAt value via the props, assign to the internal state then update this internal state.

Functional Components

Now, the move to functional based components ofcourse would lose the ability to maintain state across function calls, unless we had some way to associate it with that function call. Whereas a class would handle this initialization within its constructor. In the case of a functional component, we need a way where subsequent calls to that function cannot reinitialize the state or – in the case of out button counter example, the state would simply be reset to the one supplied by the props each render.

Let’s look at recreating the class above but as a functional component.

export function Viewer(props: IViewerProps) {
   const [state, setState] = useState(props.startAt);

   function onClick(): void {
      setState(state + 1);
   }

   return (
      <div>
         <div>{state}</div>
            <button onClick={onClick}>Click Me</button>
         </div>
   );
}

Now in this case useState is initialized the first time it’s used with the props.startAt value. This initialization does not take place again, during the lifecycle of this function, so that when you click the button it updates the state and re-renders the component without reinitializing the state. We can see this by putting console.log(`Props: ${props.startAt} State: ${state}`); after the useState line. In this case you’ll see the props value remains constant but the state changes on each click of the button.

This is great. But, what happens if the parent control actually needs to change the props. So, for example maybe we click a reset button to reset the value to the default.

useEffect

Whilst useState allows us to store state between function calls on a React component, we need a way to handle side effects, or more specifically in this example, we need ways of changing the state when the props change.

Let’s assume our parent component can set and reset the initial state for our Viewer component via the props. In fact, here’s that App component to demonstrate this


function App() {
   const [state, setState] = useState(1);

   function onReset() {
      setState(state === 0 ? 1 : 0);
   }

   return (
      <div className="App">
         <header className="App-header">
            <Viewer startAt={state} onReset={onReset}/>
         </header>
      </div>
   );
}

Note: this is a silly contrived example as we need the props to actually change – but for real work usage, imagine at some point a change in your app. is stored to localStorage and maybe. onReset loads the latest from localStorage. If that props value has now changed it will not (at this time) be reflected in the Viewer render.

You can see we’re using useState to supply the state as props to our Viewer component from our App. If you load the app as it stands, you’ll see nothing changes on the page, from our original implementation. The Viewer will keep incrementing even when reset is clicked. This is because we have no way to reset the state (remember it’s created like it would be in a constructor, i.e. when the function was first called).

This is where we use useEffect. The useEffect hook allows us to respond to changes in the props (and/or other dependencies), by adding the following code below the useState line in the Viewer component

useEffect(() => {
   setState(props.startAt);
}, [props])

Now when the props change (the [props] code denotes useEffecthas a dependency on the props value) useEffect will call setState, updating it with the latest props. We could ofcourse make this more granular by just having a dependency on [props.startAt]. We can supply an array of dependencies, any of which changes will cause useEffect code to execute.

Note: Ofcourse with a React class-based component we will also have the issue of how to reinitialize state from the props, because the props are set via the constructor. Hence this is not an issue just for functional components but in these cases useEffect is an elegant solution.

Changing your MAUI application’s title bar colour

A quick post on how to change your MAUI application’s title bar background colour.

Navigate to Resources/Styles/Colors.xaml and change the Primary colour, for example

<Color x:Key="Primary">#FF013558</Color>

You will probably want to also change the Platforms/Android/Resources/values/colours.xml, to change the top status bar background colour on Android

<color name="colorPrimaryDark">#FF013558</color>

Changing a MAUI application splash screen

This is quick post on manipulating your MAUI application’s splash screen.

  • The first thing to change is the splash.svg file in Resources/Splash. It seems to (by default) be 456 * 456 (height and width).
  • Open the .csproj file and change the following line’s Color and/or .svg
    <MauiSplashScreen 
       Include="Resources\Splash\splash.svg" 
       Color="#FF013558" 
       BaseSize="128,128" />
    

Getting Started Storybook (Latest update)

A while back I posted Getting started with Storybook. However, like with most things JavaScript, things have changed, and those instructions are now out of date and also, now, much simpler.

I have an existing React web app. so how do I add storybook ?

Note: Do not add storybook from NPM via yarn/npm, instead use the following instructions.

From you web app’s root folder run the following from the root of an existing project

npx storybook init

This will try to detect the framework being use. In the case of React this worked a treat. It will then add a stories folder to your src folder with a bunch of examples. It adds the .storybook folder with the information to tell storybook what files to look for and, ofcourse, add all the package.json dependencies etc.

Now all you need do is run

yarn storybook

So simple.

Here’s a very simple example of a .stories.tsx. I have a Header component which simpler writes a string out for a given date.

import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';

import { Header } from '../controls/Header';

export default {
  title: 'Header',
  component: Header,
  parameters: {
    layout: 'fullscreen',
  },
} as ComponentMeta<typeof Header>;

const Template: ComponentStory<typeof Header> = (args) => <Header {...args} />;

export const Display = Template.bind({});
Display.args = {
   endDateTime: new Date(Date.parse("2022-12-31T23:59:00.000Z"))
};

The Display export is shown as a view on a component within storybook and we also get the endDateTime editor for trying different inputs out.

A few tips and tricks for using Infragistics XamDataGrid

Note: This post was written a while back but sat in draft. I’ve published this now, but I’m not sure it’s relevant to the latest versions etc. so please bear this in mind.

I’m working on a project using the XamDataGrid. I’ve used the UltraWinGrid by Infragistics in the past but that doesn’t help at all when moving code from Windows Formds to WPF. So here’s a list of a few commonly required features and how to do them using the XamDataGrid.

Note: this post only refers to using version 12.2

Grid lines

Problem: By default there are no grid lines on the XamDataGrid.

Solution: In your ResourceDictionary (within Generic.xaml or wherever you prefer) add the following

<Style TargetType="{x:Type igDP:CellValuePresenter}">
   <Setter Property="BorderThickness" Value="0,0,1,1" />
   <Setter Property="BorderBrush" Value="{x:Static SystemColors.ControlLightBrush}" />
</Style>

Obviously replace the BorderBrush colour with your preferred colour.

Remove the group by area

Problem: I want to remove the group by section from the XamDataGrid.

Solution: In your ResourceDictionary (within Generic.xaml or wherever you prefer) add the following

<Style x:Key="IceGrid" TargetType="igDP:XamDataGrid">
   <Setter Property="GroupByAreaLocation" Value="None" />
</Style>

don’t forget to apply the style to your grid, i.e.

<dp:XamDataGrid Style="{StaticResource IceGrid}" DataSource="{Binding Details}">
   <!- Your grid code -->
</dp:XamDataGrid>

Column formatting

Problem: We want to change the numerical formatting for a column

Solution: We can set the EditorStyle for a field (editor doesn’t mean it will make the field editable)

<dp:XamDataGrid.FieldLayouts>
   <dp:FieldLayout>
      <dp:FieldLayout.Fields>
         <dp:Field Name="fee" Label="Fee" Width="80">
            <dp:Field.Settings>
               <dp:FieldSettings>
                  <dp:FieldSettings.EditorStyle>
                     <Style TargetType="{x:Type editors:XamNumericEditor}">
                        <Setter Property="Format" Value="0.####" />
                     </Style>
                  </dp:FieldSettings.EditorStyle>
               </dp:FieldSettings>
           </dp:Field.Settings>
        </dp:Field>         
      </dp:FieldLayout.Fields>
   </dp:FieldLayout>
</dp:XamDataGrid.FieldLayouts>

This code creates a field named fee and with the label Fee and the editor is set to only display decimal places if they actually exist.

As we’re defining the fields you’ll need to turn off auto generation of fields, as per

<dp:XamDataGrid.FieldLayoutSettings>
   <dp:FieldLayoutSettings AutoGenerateFields="False" />
</dp:XamDataGrid.FieldLayoutSettings>

First Chance Exceptions can be more important than you might think

Note: This post was written a while back but sat in draft. I’ve published this now, but I’m not sure it’s relevant to the latest versions etc. so please bear this in mind.

You’ll often see First Chance exceptions in the output window of Visual Studio without them actually causing any problems with your application, so what are they ?

A first chance exception is usually associated with debugging an application using Visual Studio, but they can occur at runtime in release builds as well.

A first chance exception occurs whenever an exception occurs. The exception is thrown at which point .NET searches for a handler (a catch) up the call stack. If a catch is found then .NET passes control to the catch handler code. So in this instance, let’s say we see a first chance exception which is caught in some library code. Then, whilst debugging, we’ll see the message that a first chance exception occurred, but it won’t cause an exception to halt an application because it’s caught. On the other hand if no catch is found the exception becomes a second chance exception within the debugging environment and this causes the debugger to break (if it’s configured to break on exceptions).

We can watch for first chance exceptions by adding an event handler to the AppDomain.CurrentDomain.FirstChanceException, for example

AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;

private void CurrentDomain_FirstChanceException(
    object sender, FirstChanceExceptionEventArgs e)
{
   // maybe we'll log e.Exception
}

So first chance exceptions don’t tend to mean there’s problems within your code however using async/await, especially with void async methods you may find first chance exceptions are the a precursor for an unhandled exception from one of these methods.

If you check out Jon Skeet’s answer on stackoverflow to Async/await exception and Visual Studio 2013 debug output behavior he succinctly describes the problems that can occur with exception handling in async/await code.

To use this great quote “When an exception occurs in an async method, it doesn’t just propagate up the stack like it does in synchronous code. Heck, the logical stack is likely not to be there any more.”. With the async/await on a Task, the task itself contains any exceptions, but when there is no task, when we’re async/await on a void method, then there’s no means to propagate any exceptions. Instead these exceptions are more likely to first appear as First Chance exceptions and then unhandled exceptions.

See also https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Xamarin.Forms lifecycle

Note: This post was written a while back but sat in draft. I’ve published this now, but I’m not sure it’s relevant to the latest versions etc. so please bear this in mind.

The following is an example of what’s generated as part of the Xamarin.Forms project.

public class App : Application
{
   protected override void OnStart()
   {
      // Handle when your app starts
   }

   protected override void OnSleep()
   {
      // Handle when your app sleeps
   }

   protected override void OnResume()
   {
      // Handle when your app resumes
   }
}
  • OnStart is called when the application starts
  • OnSleep is called when the application goes into sleep mode and/or terminates
  • OnResume is called when an application is resumed after a sleep