Sharpnado.TaskLoaderView
Free yourself from IsBusy=true! The `TaskLoaderView` is a UI component that handles all your UI loading state (Loading, Error, Result, Notification), and removes all the pain of async loading from your view models (try catch / async void / IsBusy / HasErrors / base view models / ...) thanks to its brother the `TaskLoaderNotifier`.
Install / Use
/learn @roubachof/Sharpnado.TaskLoaderViewREADME
TaskLoaderView 2.0: Let's burn IsBusy=true!
The TaskLoaderView is a UI component that handles all your UI loading state (Loading, Error, Result, Notification), and removes all the pain of async loading from your view models (try catch / async void / IsBusy / HasErrors / base view models / ...) thanks to its brother the TaskLoaderNotifier.
| MAUI Supported platforms |
|----------------------------|
| <img src="Docs/maui_logo.png" height="100" /> |
| |
| :heavy_check_mark: Android |
| :heavy_check_mark: iOS |
| :heavy_check_mark: Windows |
| :heavy_check_mark: macOS |
Featuring:
- Default layout for all loading states (
Loading,Error,Success,Notification,Refresh) - Stylable layouts including fonts, accent colors, error images, ...
- Support for async
ICommandwithTaskLoaderCommandandCompositeTaskLoaderNotifier - Any states are overridable with user custom views and easily positioned with AbsoluteLayout properties
- Support for
Xamarin.Forms.Skeletonnuget package - Support for refresh scenarios, and error while refreshing with the
ErrorNotificationView - Support loading task on demand with the
NotStartedstate TaskLoaderNotifierfor theViewModelside taking care of all the error handling and theIsBusynonsense

It has been tested on Android, iOS, Windows and MacOS platforms through the Retronado sample app.
It uses the Sharpnado's TaskMonitor.
Installation
MAUI
Add the Sharpnado.Maui.TaskLoaderView nuget package.
And call the initializer in your MauiProgram.cs file:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureTaskLoader(true) // logger enabled
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
return builder.Build();
}
}
2.5.0 MAUI support \o/ and TemplatedTaskLoader
Version 2.5.0 now supports .Net MAUI.
New TemplatedTaskLoader: it does the same job as the TaskLoaderView but using ControlTemplate instead of a absolute layout of views.
<cv:TemplatedTaskLoader x:Name="LoaderView"
Grid.Row="1"
Style="{StaticResource TemplatedLoaderLongLoading}"
TaskLoaderNotifier="{Binding Loader}">
<cv:TemplatedTaskLoader.ResultControlTemplate>
<ControlTemplate>
...
</ControlTemplate>
</cv:TemplatedTaskLoader.ResultControlTemplate>
<cv:TemplatedTaskLoader.ErrorControlTemplate>
<ControlTemplate>
...
</ControlTemplate>
</cv:TemplatedTaskLoader.ErrorControlTemplate>
<cv:TemplatedTaskLoader.LoadingControlTemplate>
<ControlTemplate>
...
</ControlTemplate>
</cv:TemplatedTaskLoader.LoadingControlTemplate>
</cv:TemplatedTaskLoader>
...
<Style x:Key="TemplatedLoaderLongLoading"
TargetType="customViews:TemplatedTaskLoader">
<Setter Property="LoadingControlTemplate" Value="{StaticResource LottieRocketControlTemplate}" />
<Setter Property="ErrorControlTemplate" Value="{StaticResource ErrorViewControlTemplate}" />
</Style>
<ControlTemplate x:Key="LottieRocketControlTemplate">
<forms:AnimationView HorizontalOptions="Center"
VerticalOptions="Center"
HeightRequest="200"
WidthRequest="200"
Animation="delivery_truck_animation.json"
IsAnimating="{Binding Source={RelativeSource AncestorType={x:Type customViews:TemplatedTaskLoader}},
Path=TaskLoaderNotifier.ShowLoader}"
RepeatMode="Infinite" />
<ControlTemplate x:Key="ErrorViewControlTemplate">
<StackLayout HorizontalOptions="Center"
VerticalOptions="Center"
BindingContext="{Binding Source={RelativeSource AncestorType={x:Type customViews:TemplatedTaskLoader}},
Path=TaskLoaderNotifier}"
IsVisible="False"
Orientation="Vertical"
Spacing="10">
<Frame Style="{StaticResource FrameCircle}"
WidthRequest="{StaticResource SizeTaskLoaderIcon}"
HeightRequest="{StaticResource SizeTaskLoaderIcon}"
Margin="0,0,0,10"
BackgroundColor="{StaticResource ColorPrimary}">
<Image HorizontalOptions="Center"
VerticalOptions="Center"
Source="{Binding Error,
Converter={converters:ExceptionToImageSourceConverter}}" />
</Frame>
<Label Style="{StaticResource TextBodySecondary}"
WidthRequest="300"
Margin="0,0,0,20"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
MaxLines="2"
Text="{Binding Error,
Converter={converters:ExceptionToErrorMessageConverter}}" />
<sho:Shadows CornerRadius="10"
Shades="{StaticResource ShadowAccentBottom}">
<Button Style="{StaticResource ButtonAccent}"
HorizontalOptions="Center"
VerticalOptions="End"
Command="{Binding ReloadCommand}"
Text="{x:Static loc:GlobalResources.Common_Retry}" />
</sho:Shadows>
</StackLayout>
</ControlTemplate>
For all new developments, I recommend now to use the TemplatedTaskLoader, and use it with a Snackbar to handle the error notifications.
There is an detailed example on how to use it here:
https://github.com/roubachof/Sharpnado.TaskLoaderView/tree/master/Retronado.Maui/Views/CommandsPage.xaml
and here:
https://github.com/roubachof/Sharpnado.TaskLoaderView/tree/master/Retronado.Maui/ViewModels/CommandsPageViewModel.cs
New CompositeTaskLoaderNotifier builder and simplified wiring
You can use the new builder for the CompositeTaskLoader that will wire for you the ShowErrorNotification of your loaders, and the errors from your TaskLoaderCommand:
CompositeNotifier = CompositeTaskLoaderNotifier.ForCommands()
.WithLoaders(Loader)
.WithCommands(BuyGameCommand, PlayTheGameCommand)
.Build();
And bind the ShowLastError and LastError property to your Snackbar:
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<views:NavigationToolBar Title="{loc:Translate Commands_Title}"
Grid.Row="0"
BackgroundColor="{StaticResource TopElementBackground}"
Theme="Standard" />
<RefreshView Grid.Row="1"
IsRefreshing="{Binding Loader.ShowRefresher}"
RefreshColor="{StaticResource AccentColor}"
Command="{Binding Loader.RefreshCommand}">
<ScrollView>
<tlv:TemplatedTaskLoader TaskLoaderNotifier="{Binding Loader}">
<tlv:TemplatedTaskLoader.ResultControlTemplate>
<ControlTemplate>
<Grid RowDefinitions="300,*"
x:DataType="viewModels:CommandsPageViewModel"
BindingContext="{Binding Source={RelativeSource
AncestorType={x:Type viewModels:CommandsPageViewModel}}}">
<Image Grid.Row="0"
skeleton:Skeleton.BackgroundColor="{StaticResource GreyBackground}"
skeleton:Skeleton.IsBusy="{Binding Loader.ShowLoader}"
Aspect="Fill"
Source="{Binding Loader.Result.ScreenshotUrl}" />
<sho:MaterialFrame Grid.Row="0"
Padding="15,5,15,30"
VerticalOptions="End"
CornerRadius="0"
MaterialBlurStyle="Dark"
MaterialTheme="AcrylicBlur">
<Grid Padding="0"
ColumnDefinitions="*,60"
RowDefinitions="40,20,20"
RowSpacing="0">
<Label Grid.Row="0"
Grid.Column="0"
Style="{StaticResource GameName}"
Margin="0,0,10,0"
skeleton:Skeleton.BackgroundColor="{StaticResource GreyBackground}"
skeleton:Skeleton.IsBusy="{Binding Loader.ShowLoader}"
Text="{Binding Loader.Result.Name}" />
