The DelayedView: a better LazyView

The DelayedView:  a better LazyView
https://github.com/roubachof/Sharpnado.Tabs

You may know the LazyView, which builds lazily the views it wraps.

Doing so, it reduces your page loading time, especially if you have complex pages.

So the lazy view is great for reducing your loading time, but not so great to keep your app responsive. Let's consider this design:

You can see that each tab has a lot of views and UI effects embedded.

Here is the matching xaml:

<Grid Margin="16,0" RowDefinitions="120,*,95">

    <ContentView ZIndex="100">
        <ContentView.Background>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                <GradientStop Offset="0.2" Color="{StaticResource Black}" />
                <GradientStop Offset="1" Color="Transparent" />
            </LinearGradientBrush>
        </ContentView.Background>
        <Image Margin="0,20" Source="logo.png">
            <Image.Shadow>
                <Shadow Brush="{StaticResource Black}"
                        Opacity="0.9"
                        Radius="30"
                        Offset="0,10" />
            </Image.Shadow>
        </Image>
    </ContentView>

    <tabs:ViewSwitcher x:Name="Switcher"
                       Grid.RowSpan="3"
                       Margin="0"
                       Animate="True"
                       SelectedIndex="{Binding SelectedViewModelIndex, 
                                      Mode=TwoWay}">
        <tabs:LazyView x:TypeArguments="views:TabM"
                       AccentColor="{StaticResource Primary}"
                       Animate="True"
                       BindingContext="{Binding HomePageViewModel}"
                       UseActivityIndicator="True" />
        <tabs:LazyView x:TypeArguments="views:TabA"
                       AccentColor="{StaticResource Primary}"
                       Animate="True"
                       UseActivityIndicator="True" />
        <tabs:LazyView x:TypeArguments="views:TabU"
                       AccentColor="{StaticResource Primary}"
                       Animate="True"
                       UseActivityIndicator="True" />
        <tabs:LazyView x:TypeArguments="views:TabI" Animate="True" />
    </tabs:ViewSwitcher>

    <ContentView Grid.Row="2">
        <ContentView.Background>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                <GradientStop Offset="0.0" Color="Transparent" />
                <GradientStop Offset="0.5" Color="{StaticResource Black}" />
            </LinearGradientBrush>
        </ContentView.Background>
        <tabs:TabHostView WidthRequest="250"
                          HeightRequest="60"
                          Padding="20,0"
                          HorizontalOptions="Center"
                          BackgroundColor="{StaticResource Gray900}"
                          CornerRadius="30"
                          IsSegmented="True"
                          Orientation="Horizontal"
                          SegmentedOutlineColor="{StaticResource Gray950}"
                          SelectedIndex="{Binding 
                                         Source={x:Reference Switcher}, 
                                         Path=SelectedIndex, 
                                         Mode=TwoWay}"
                          TabType="Fixed">
            <tabs:TabHostView.Shadow>
                <Shadow Brush="{StaticResource Primary}"
                        Opacity="0.7"
                        Radius="30"
                        Offset="0,10" />
            </tabs:TabHostView.Shadow>
            <tabs:BottomTabItem Style="{StaticResource BottomTab}" 
                                Label="M" />
            <tabs:BottomTabItem Style="{StaticResource BottomTab}" 
                                Label="A">
                <tabs:BottomTabItem.Badge>
                    <tabs:BadgeView BackgroundColor="{StaticResource Tertiary}" 
                                    Text="new" />
                </tabs:BottomTabItem.Badge>
            </tabs:BottomTabItem>
            <tabs:UnderlinedTabItem FontFamily="OpenSansExtraBold"
                                    Label="U"
                                    LabelSize="36"
                                    SelectedTabColor="{StaticResource Primary}"
                                    UnselectedLabelColor="{StaticResource White}" />
            <tabs:BottomTabItem Style="{StaticResource BottomTab}"
                                Padding="0,0,10,0"
                                Label="I">
                <tabs:BottomTabItem.Badge>
                    <tabs:BadgeView BackgroundColor="{StaticResource Tertiary}" 
                                    Text="2" />
                </tabs:BottomTabItem.Badge>
            </tabs:BottomTabItem>
        </tabs:TabHostView>
    </ContentView>
</Grid>

Now let's zoom to the ViewSwitcher

<tabs:ViewSwitcher x:Name="Switcher"
                           Grid.RowSpan="3"
                           Margin="0"
                           Animate="True"
                           SelectedIndex="{Binding SelectedViewModelIndex, 
                                          Mode=TwoWay}">
            <tabs:LazyView x:TypeArguments="views:TabM"
                              AccentColor="{StaticResource Primary}"
                              Animate="True"
                              BindingContext="{Binding HomePageViewModel}"
                              UseActivityIndicator="True" />
            <tabs:LazyView x:TypeArguments="views:TabA"
                              AccentColor="{StaticResource Primary}"
                              Animate="True"
                              UseActivityIndicator="True" />
            <tabs:LazyView x:TypeArguments="views:TabU"
                              AccentColor="{StaticResource Primary}"
                              Animate="True"
                              UseActivityIndicator="True" />
            <tabs:LazyView x:TypeArguments="views:TabI" Animate="True" />
</tabs:ViewSwitcher>

Since the children of our view switcher are pretty complex, when we will try to switch tabs, it will induce a nasty lag (the time for the views to be built).

But, if we add a simple async delay before building our UI and gave loading feedback to our user, the app will feel really smoother, no lag will be induced.

We just need to change all LazyView by DelayedView, you will achieve a far better result in terms of app smoothness.

<tabs:ViewSwitcher x:Name="Switcher"
                           Grid.RowSpan="3"
                           Margin="0"
                           Animate="True"
                           SelectedIndex="{Binding SelectedViewModelIndex, 
                                          Mode=TwoWay}">
            <tabs:DelayedView x:TypeArguments="views:TabM"
                              AccentColor="{StaticResource Primary}"
                              Animate="True"
                              BindingContext="{Binding HomePageViewModel}"
                              UseActivityIndicator="True" />
            <tabs:DelayedView x:TypeArguments="views:TabA"
                              AccentColor="{StaticResource Primary}"
                              Animate="True"
                              UseActivityIndicator="True" />
            <tabs:DelayedView x:TypeArguments="views:TabU"
                              AccentColor="{StaticResource Primary}"
                              Animate="True"
                              UseActivityIndicator="True" />
            <tabs:DelayedView x:TypeArguments="views:TabI" Animate="True" />
</tabs:ViewSwitcher>
0:00
/

What happens here?

Whereas it's the same loading time, the DelayedView give a far better experience to our user by giving him a simple feedback saying "we acknowledge your action pal, everything is A-OK!".

The implementation is super simple:

public class DelayedView : ALazyView
{
    public static readonly BindableProperty ViewProperty = BindableProperty.Create(
        nameof(View),
        typeof(View),
        typeof(DelayedView),
        default(View));

    public View View
    {
        get => (View)GetValue(ViewProperty);
        set => SetValue(ViewProperty, value);
    }

    public int DelayInMilliseconds { get; set; } = 200;

    public override void LoadView()
    {
        if (IsLoaded)
        {
            return;
        }

        TaskMonitor.Create(
            async () =>
                {
                    await Task.Delay(DelayInMilliseconds);
                    if (IsLoaded)
                    {
                        return;
                    }

                    IsLoaded = true;
                    Content = View;
                });
    }
}

You can use a DelayedView wherever your want.

For example as a container for a complex view hierarchy at the root of a content page.