TaskLoaderView comes back in Style with DataTemplate

TaskLoaderView comes back in Style with DataTemplate
https://github.com/roubachof/Sharpnado.TaskLoaderView

Since version 2.0, the TaskLoaderView supports custom views for your Tasks states.
So you can define, for example, really cool Lottie views with great animations.

<customViews:TaskLoaderView Grid.Row="2"
                            Style="{StaticResource TaskLoaderStyle}"
                            TaskLoaderNotifier="{Binding Loader}">
    <customViews:TaskLoaderView.LoadingView>
        <lottie:AnimationView x:Name="LoadingLottie"
                              AbsoluteLayout.LayoutFlags="PositionProportional"
                              AbsoluteLayout.LayoutBounds="0.5, 0.4, 120, 120"
                              HorizontalOptions="FillAndExpand"
                              VerticalOptions="FillAndExpand"
                              Animation="{Binding Loader.ShowLoader, 
                                Converter={StaticResource CyclicLoadingLottieConverter}}"
                              AutoPlay="True"
                              Loop="True" />
    </customViews:TaskLoaderView.LoadingView>

    <customViews:TaskLoaderView.EmptyView>
        <StackLayout AbsoluteLayout.LayoutFlags="PositionProportional" 
                     AbsoluteLayout.LayoutBounds="0.5, 0.4, 300, 180">

            <lottie:AnimationView HorizontalOptions="FillAndExpand"
                                  VerticalOptions="FillAndExpand"
                                  Animation="empty_state.json"
                                  AutoPlay="True"
                                  Loop="True" />

            <Label Style="{StaticResource TextBody}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center"
                   Text="{loc:Translate Empty_Screen}"
                   TextColor="White" />
            <Button Style="{StaticResource TextBody}"
                    HeightRequest="40"
                    Margin="0,20,0,0"
                    Padding="25,0"
                    HorizontalOptions="Center"
                    BackgroundColor="{StaticResource TopElementBackground}"
                    Command="{Binding Loader.ReloadCommand}"
                    Text="{loc:Translate ErrorButton_Retry}"
                    TextColor="White" />
        </StackLayout>
    </customViews:TaskLoaderView.EmptyView>

    <customViews:TaskLoaderView.ErrorView>
        <StackLayout AbsoluteLayout.LayoutFlags="PositionProportional" 
                     AbsoluteLayout.LayoutBounds="0.5, 0.4, 300, 180">

            <lottie:AnimationView HorizontalOptions="FillAndExpand"
                                  VerticalOptions="FillAndExpand"
                                  Animation="{Binding Loader.Error, 
                                    Converter={StaticResource ExceptionToLottieConverter}}"
                                  AutoPlay="True"
                                  Loop="True" />

            <Label Style="{StaticResource TextBody}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center"
                   Text="{Binding Loader.Error, Converter={StaticResource ExceptionToErrorMessageConverter}}"
                   TextColor="White" />
            <Button Style="{StaticResource TextBody}"
                    HeightRequest="40"
                    Margin="0,20,0,0"
                    Padding="25,0"
                    HorizontalOptions="Center"
                    BackgroundColor="{StaticResource TopElementBackground}"
                    Command="{Binding Loader.ReloadCommand}"
                    Text="{loc:Translate ErrorButton_Retry}"
                    TextColor="White" />
        </StackLayout>
    </customViews:TaskLoaderView.ErrorView>

    ...

</customViews:TaskLoaderView>

lottie animation

So this is great! But we have to copy paste all the custom views each time we want to use the TaskLoaderView.
It kinds of pollute our view files with the same xaml.

So what if we could just declare our custom views once and apply them transparently to all our TaskLoaderView?

Styles and DataTemplate

This is when the DataTemplate comes to the party.
Since 2.1.0 TaskLoaderView custom views properties supports now classic View but also DataTemplate.
We can now declare our custom views as DataTemplate and reference them in our Styles:

<ResourceDictionary>

    <DataTemplate x:Key="LoadingLottieDataTemplate">
        <lottie:AnimationView x:Name="LoadingLottie"
                                AbsoluteLayout.LayoutFlags="PositionProportional"
                                AbsoluteLayout.LayoutBounds="0.5, 0.4, 120, 120"
                                HorizontalOptions="FillAndExpand"
                                VerticalOptions="FillAndExpand"
                                Animation="{Binding Loader.ShowLoader, 
                                    Converter={StaticResource CyclicLoadingLottieConverter}}"
                                IsPlaying="True"
                                Loop="True" />
    </DataTemplate>

    <DataTemplate x:Key="EmptyLottieDataTemplate">
        <StackLayout AbsoluteLayout.LayoutFlags="PositionProportional"
                     AbsoluteLayout.LayoutBounds="0.5, 0.4, 300, 180"
                     BindingContext="{Binding 
                        Source={RelativeSource AncestorType={x:Type customViews:TaskLoaderView}}, 
                        Path=TaskLoaderNotifier}">

            <lottie:AnimationView HorizontalOptions="FillAndExpand"
                                    VerticalOptions="FillAndExpand"
                                    Animation="empty_state.json"
                                    IsPlaying="True"
                                    Loop="True" />

            <Label Style="{StaticResource TextBody}"
                    HorizontalOptions="Center"
                    VerticalOptions="Center"
                    Text="{loc:Translate Empty_Screen}"
                    TextColor="White" />
            <Button Style="{StaticResource TextBody}"
                    HeightRequest="40"
                    Margin="0,20,0,0"
                    Padding="25,0"
                    HorizontalOptions="Center"
                    BackgroundColor="{StaticResource TopElementBackground}"
                    Command="{Binding ReloadCommand}"
                    Text="{loc:Translate ErrorButton_Retry}"
                    TextColor="White" />
        </StackLayout>
    </DataTemplate>

    <DataTemplate x:Key="ErrorLottieDataTemplate">
        <StackLayout AbsoluteLayout.LayoutFlags="PositionProportional"
                        AbsoluteLayout.LayoutBounds="0.5, 0.4, 300, 180"
                        BindingContext="{Binding 
                            Source={RelativeSource AncestorType={x:Type customViews:TaskLoaderView}},
                            Path=TaskLoaderNotifier}">

            <lottie:AnimationView HorizontalOptions="FillAndExpand"
                                    VerticalOptions="FillAndExpand"
                                    Animation="{Binding Error, 
                                        Converter={StaticResource ExceptionToLottieConverter}}"
                                    IsPlaying="True"
                                    Loop="True" />

            <Label Style="{StaticResource TextBody}"
                    HorizontalOptions="Center"
                    VerticalOptions="Center"
                    Text="{Binding Error, Converter={StaticResource ExceptionToErrorMessageConverter}}"
                    TextColor="White" />
            <Button Style="{StaticResource TextBody}"
                    HeightRequest="40"
                    Margin="0,20,0,0"
                    Padding="25,0"
                    HorizontalOptions="Center"
                    BackgroundColor="{StaticResource TopElementBackground}"
                    Command="{Binding ReloadCommand}"
                    Text="{loc:Translate ErrorButton_Retry}"
                    TextColor="White" />
        </StackLayout>
    </DataTemplate>

    <Style x:Key="TaskLoaderStyle" TargetType="customViews:TaskLoaderView">
        <Setter Property="FontFamily" Value="{StaticResource FontAtariSt}" />
        <Setter Property="NotificationBackgroundColor" Value="{StaticResource TosWindows}" />
        <Setter Property="NotificationTextColor" Value="{StaticResource TextPrimaryColor}" />

        <Setter Property="LoadingView" Value="{StaticResource LoadingLottieDataTemplate}" />
        <Setter Property="EmptyView" Value="{StaticResource EmptyLottieDataTemplate}" />
        <Setter Property="ErrorView" Value="{StaticResource ErrorLottieDataTemplate}" />
    </Style>

</ResourceDictionary>

We could even create an implicit style, and we won't have to reference any style in our TaskLoaderView:

    <Style TargetType="customViews:TaskLoaderView">
        <Setter Property="FontFamily" Value="{StaticResource FontAtariSt}" />
        <Setter Property="NotificationBackgroundColor" Value="{StaticResource TosWindows}" />
        <Setter Property="NotificationTextColor" Value="{StaticResource TextPrimaryColor}" />

        <Setter Property="LoadingView" Value="{StaticResource LoadingLottieDataTemplate}" />
        <Setter Property="EmptyView" Value="{StaticResource EmptyLottieDataTemplate}" />
        <Setter Property="ErrorView" Value="{StaticResource ErrorLottieDataTemplate}" />
    </Style>

I would also like to highlight an unknown feature of Xamarin.Forms: Relative bindings

Thanks to them, you can easily bind your parent's control properties from anywhere.
It's a perfect fit for our DataTemplates:

<DataTemplate x:Key="ErrorLottieDataTemplate">
    <StackLayout AbsoluteLayout.LayoutFlags="PositionProportional"
                 AbsoluteLayout.LayoutBounds="0.5, 0.4, 300, 180"
                 BindingContext="{Binding 
                     Source={RelativeSource AncestorType={x:Type customViews:TaskLoaderView}},
                     Path=TaskLoaderNotifier}">

You can find the code here: LottieViewsPage.xaml

But also

  • You can set the ReloadCommand and the RefreshCommand, if you have specific scenarios.
<customViews:TaskLoaderView Style="{StaticResource TaskLoaderStyle}"
                            RefreshCommand="{Binding RefreshCommand}"
                            ReloadCommand="{Binding ReloadCommand}"
                            TaskLoaderNotifier="{Binding Loader}">
  • The underlying TaskMonitor is now exposed, so you can await on the Task if you have to chain some async code in your view models.
private async Task ChainTasks()
{
    await Task.Delay(1000);
    // The TaskCompleted property will never raise an exception
    await Loader.CurrentLoadingTask.TaskCompleted;
    await Task.Delay(1000);
}