StateView.Maui 1.0.2

dotnet add package StateView.Maui --version 1.0.2
                    
NuGet\Install-Package StateView.Maui -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="StateView.Maui" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StateView.Maui" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="StateView.Maui" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add StateView.Maui --version 1.0.2
                    
#r "nuget: StateView.Maui, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package StateView.Maui@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=StateView.Maui&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=StateView.Maui&version=1.0.2
                    
Install as a Cake Tool

StateView.Maui

Description

StateView.Maui is a .NET MAUI control for showing loading, saving, success, error, empty, and custom UI states around normal page content.

The main control, StateView, keeps your regular content in place and swaps in templated state content when CurrentState changes. State UI is defined with DataTemplate instances, so state views are created fresh when rendered.

Features

  • Built-in states: None, Loading, Saving, Success, Error, Empty, and Custom.
  • Template-based state UI through StateTemplate.
  • Custom state matching with CurrentCustomStateKey and CustomStateKey.
  • Repeated skeleton rows with RepeatCount and RepeatTemplate.
  • Lightweight pulsing SkeletonView for loading placeholders.
  • Optional fade transitions through AnimateStateChanges and AnimationDuration.
  • StateToBooleanConverter for binding state values to boolean UI properties.

Installation

dotnet add package StateView.Maui --version 1.0.2

Use the XAML namespace in pages that render StateView.Maui controls:

xmlns:state="clr-namespace:StateView.Maui;assembly=StateView.Maui"

Usage

<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:state="clr-namespace:StateView.Maui;assembly=StateView.Maui">

    <state:StateView CurrentState="{Binding CurrentState}">
        <state:StateView.StateTemplates>
            <state:StateTemplate StateKey="Loading">
                <state:StateTemplate.Template>
                    <DataTemplate>
                        <VerticalStackLayout Padding="24" Spacing="12" HorizontalOptions="Center" VerticalOptions="Center">
                            <ActivityIndicator IsRunning="True" />
                            <Label Text="Loading..." HorizontalTextAlignment="Center" />
                        </VerticalStackLayout>
                    </DataTemplate>
                </state:StateTemplate.Template>
            </state:StateTemplate>

            <state:StateTemplate StateKey="Empty">
                <state:StateTemplate.Template>
                    <DataTemplate>
                        <Label
                            Padding="24"
                            Text="No items found."
                            HorizontalTextAlignment="Center"
                            VerticalTextAlignment="Center" />
                    </DataTemplate>
                </state:StateTemplate.Template>
            </state:StateTemplate>
        </state:StateView.StateTemplates>

        <CollectionView ItemsSource="{Binding Items}">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <Grid Padding="12">
                        <Label Text="{Binding Title}" />
                    </Grid>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </state:StateView>
</ContentPage>

State.None shows the normal content. Any other state looks for the first matching StateTemplate. If no template matches, the normal content remains visible.

ViewModel

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using StateView.Maui;

public sealed class ItemsViewModel : INotifyPropertyChanged
{
    private State _currentState = State.Loading;

    public event PropertyChangedEventHandler? PropertyChanged;

    public ObservableCollection<Item> Items { get; } = [];

    public State CurrentState
    {
        get => _currentState;
        set => SetProperty(ref _currentState, value);
    }

    public async Task LoadAsync()
    {
        CurrentState = State.Loading;

        await Task.Delay(500);

        Items.Clear();
        CurrentState = Items.Count == 0 ? State.Empty : State.None;
    }

    private void SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
        {
            return;
        }

        field = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public sealed record Item(string Title);

Custom States

Use State.Custom when one state slot needs multiple named templates. Set CurrentCustomStateKey on StateView, then match it with CustomStateKey on a StateTemplate.

<state:StateView
    CurrentState="{Binding CurrentState}"
    CurrentCustomStateKey="{Binding CurrentCustomStateKey}">

    <state:StateView.StateTemplates>
        <state:StateTemplate StateKey="Custom" CustomStateKey="Offline">
            <state:StateTemplate.Template>
                <DataTemplate>
                    <Border Padding="20">
                        <Label Text="Offline mode is active." />
                    </Border>
                </DataTemplate>
            </state:StateTemplate.Template>
        </state:StateTemplate>
    </state:StateView.StateTemplates>

    <CollectionView ItemsSource="{Binding Items}" />
</state:StateView>
CurrentCustomStateKey = "Offline";
CurrentState = State.Custom;

Skeleton And Repeat Templates

SkeletonView is a pulsing BoxView placeholder. For repeated loading rows, set RepeatCount and provide a RepeatTemplate.

<state:StateTemplate StateKey="Loading" RepeatCount="5">
    <state:StateTemplate.RepeatTemplate>
        <DataTemplate>
            <Grid Padding="16" RowDefinitions="20,14" RowSpacing="10">
                <state:SkeletonView />
                <state:SkeletonView Grid.Row="1" WidthRequest="190" HorizontalOptions="Start" />
            </Grid>
        </DataTemplate>
    </state:StateTemplate.RepeatTemplate>
</state:StateTemplate>

When RepeatCount is greater than 1, repeated content is created from RepeatTemplate. If RepeatTemplate is missing, no repeated placeholder content is created.

Animated Transitions

State changes fade by default. Use AnimateStateChanges to enable or disable transitions and AnimationDuration to control the fade duration in milliseconds.

<state:StateView
    AnimateStateChanges="True"
    AnimationDuration="250"
    CurrentState="{Binding CurrentState}">
    <CollectionView ItemsSource="{Binding Items}" />
</state:StateView>

Rapid state changes are coordinated so obsolete transitions are cancelled before the latest state is applied.

Why

State-heavy screens often need loading, empty, error, success, and custom overlays without scattering conditional UI across the page. StateView.Maui keeps that logic in one control while leaving each state fully templated and app-specific.

Sample App

The sample app is in SAMPLE/StateView.Maui.Sample.

It demonstrates Loading, Empty, Error, Success, Custom/Offline, skeleton placeholders, animated transitions, and rapid state changes.

Build

dotnet restore StateView.Maui.sln
dotnet build StateView.Maui/StateView.Maui.csproj -f net10.0-android
dotnet build StateView.Maui/StateView.Maui.csproj -f net10.0-ios
dotnet build StateView.Maui/StateView.Maui.csproj -f net10.0-maccatalyst

Requirements

  • .NET MAUI with .NET 10.
  • Supported target frameworks: net10.0-android, net10.0-ios, and net10.0-maccatalyst.
  • Android API level 23 or later.
  • iOS 15.0 or later.
  • Mac Catalyst 15.0 or later.

Known Limitations

  • The attached property compatibility layer is not included in v1.
  • This is not a drop-in replacement for Xamarin.Forms.StateSquid.
  • Windows is not included.
  • Advanced theme and preset skeleton APIs are not included.

Local development with ProjectReference

This library is intended to be consumed primarily as a NuGet package.

For local development, debugging, or testing changes before publishing a new package, you can also clone the repository and reference the project directly with a ProjectReference from a consuming .NET MAUI app.

This mode is optional and should be treated as a development-only workflow. Normal consumers should use the NuGet package.

Enable ProjectReference mode locally

To enable local ProjectReference mode, create a file named:

Directory.Build.local.props

in the same directory as:

Directory.Build.props

Do not commit this file. It is meant to contain local machine/developer settings only.

Recommended local configuration:

<Project>
	<PropertyGroup>
		<UseAsProjectReference>true</UseAsProjectReference>
		<OverrideAndroidSpecificVersion>36.0</OverrideAndroidSpecificVersion>
		
		
	</PropertyGroup>
</Project>

What this does

By default, the project uses package-oriented, generic .NET MAUI platform TFMs, for example:

net10.0-ios
net10.0-android

When UseAsProjectReference is enabled, the project can adjust its target frameworks to match the platform-specific target required by a consuming app.

For example, with:

<OverrideAndroidSpecificVersion>36.0</OverrideAndroidSpecificVersion>

the Android target becomes:

net10.0-android36.0

This is useful when a consuming app targets a specific Android platform version and the library is referenced directly as a project instead of as a NuGet package.

If UseAsProjectReference=true is set and OverrideAndroidSpecificVersion is not provided, the project is configured to fall back to Android 36.0 for ProjectReference mode. Setting the value explicitly is still recommended because it makes the consuming setup easier to read.

Optional iOS override

If a consuming app requires a specific iOS platform version, use:

<OverrideIosSpecificVersion>26.0</OverrideIosSpecificVersion>

In that case, the iOS target becomes:

net10.0-ios26.0

If OverrideIosSpecificVersion is not set, the iOS target remains generic:

net10.0-ios

Optional MacCatalyst override

Projects that include MacCatalyst also support:

<OverrideMacCatalystSpecificVersion>26.0</OverrideMacCatalystSpecificVersion>

When set together with UseAsProjectReference=true, this changes the MacCatalyst target to:

net10.0-maccatalyst26.0

Important notes

  • Directory.Build.local.props is for local development only.
  • Do not commit Directory.Build.local.props.
  • Normal NuGet builds and CI builds should run without this local file.
  • When the local file is not present, UseAsProjectReference defaults to false.
  • When UseAsProjectReference is false, the project uses its normal package-oriented target frameworks.
  • If the project supports MacCatalyst, OverrideMacCatalystSpecificVersion can be used in the same way as the Android and iOS overrides.
  • If you switch between package mode and project-reference mode, clean bin and obj folders before rebuilding.
  • Restore and build should be performed in the same mode. If restore runs with local overrides enabled, build should use the same overrides.
  • If Rider keeps building against an old Android/iOS target after switching modes, reload all projects. If the problem persists, use File > Invalidate Caches... and reopen the solution.

Packing and testing the NuGet package locally

When creating or testing the NuGet package, make sure the local ProjectReference overrides are disabled. Otherwise the package can be produced with development-specific target frameworks.

Before packing, temporarily rename the local props file if it exists:

mv Directory.Build.local.props Directory.Build.local.props.disabled

Then clean generated folders from the repository root:

find . -type d \( -name bin -o -name obj \) -prune -exec rm -rf {} +

Pack the library by calling dotnet pack directly on the library .csproj, not on the solution root:

dotnet pack StateView.Maui/StateView.Maui.csproj \
  -c Release \
  -o ./local-nuget

Packing the concrete library project avoids unintentionally building sample apps, tests, or other projects in the solution.

After packing, you can re-enable your local development settings:

mv Directory.Build.local.props.disabled Directory.Build.local.props

To inspect the generated package contents:

unzip -l ./local-nuget/StateView.Maui.1.0.2.nupkg | grep "lib/"

With .NET MAUI/.NET 10, it is normal for the generated .nupkg to contain platform-normalized asset folders such as:

lib/net10.0-android36.0/
lib/net10.0-ios26.0/

even when the project file declares generic TFMs such as net10.0-android or net10.0-ios. Those platform versions are resolved by the installed .NET SDK/workloads during build/pack.

To test the package without publishing it, add ./local-nuget as a local NuGet source in a consuming app and use the normal PackageReference workflow. This is the best way to verify the package as a real consumer would use it.

License

StateView.Maui is released under the MIT License. See the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net10.0-android36.0 is compatible.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst26.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 138 6/12/2026
1.0.0 110 5/27/2026