I’m porting a Xamarin Android Native app to MAUI. The app has one activity and a number of fragments. The activity displays one fragment at a time (depending on the user’s choices) but it also displays some common UI elements in a “header” and a “footer” layout. The use of fragments allows Android to take care of the UI navigation (think back button).
The only way I can see to have something similar in MAUI will be to create a content page (the activity) and number of content views (the fragments). I will have to implement the logic around swapping the content views and handling the back button.
Is there a better way to do this in MAUI?
If you look at ContentView.ControlTemplate
and the fact that you can assign ControlTemplate
to instantiate/modify the appearance of a ContentView
would make it a candidate for a fragment.
The other thing is if you look at Grid
and take into account that every child of a grid, particularly if it occupies the same Grid.Row
, Grid.Column
will stack into the same cell forms the basis of building a fragment manager with a stack.
// FragmentManager.cs
using CommunityToolkit.Mvvm.Input;
namespace MauiFragmentDemo;
public partial class FragmentManager : Grid
{
public FragmentManager()
{
this.Loaded += (s, e) =>
{
if (InitialFragment is not null)
{
this.Dispatcher.DispatchAsync(async () => await Push(InitialFragment));
}
};
}
public int FragmentCount => this.Children.Count;
[RelayCommand]
public async Task Push(ControlTemplate fragment)
{
ContentView contentView = new ContentView()
{
ControlTemplate = fragment
};
Grid grid = new Grid()
{
Background = Colors.White,
TranslationX = this.Width
};
grid.Add(contentView);
Add(grid);
OnPropertyChanged(nameof(FragmentCount));
await grid.TranslateTo(0, 0, 250);
PopCommand.NotifyCanExecuteChanged();
}
[RelayCommand(CanExecute = nameof(CanPop))]
public async Task Pop()
{
if (this.Children.Count == 0)
{
return;
}
if (this.Children[Children.Count - 1] is not Grid grid)
{
return;
}
await grid.TranslateTo(this.Width, 0, 250);
this.Children.Remove(grid);
OnPropertyChanged(nameof(FragmentCount));
PopCommand.NotifyCanExecuteChanged();
}
public bool CanPop => this.Children.Count > 0;
public ControlTemplate? InitialFragment { get; set; } = null;
}
Here's a sample test of the above fragment manager:
<!-- MainPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="MauiFragmentDemo.MainPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiFragmentDemo"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Name="mainPage">
<ContentPage.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="buttonFragment">
<Grid><Button HorizontalOptions="Center" Text="{Binding ., Source={x:Static sys:DateTime.Now}, StringFormat='Button Fragment: {0}'}" VerticalOptions="Center" /></Grid>
</ControlTemplate>
<ControlTemplate x:Key="entryFragment">
<Grid><Entry HorizontalOptions="Center" Placeholder="{Binding ., Source={x:Static sys:DateTime.Now}, StringFormat='Entry Fragment: {0}'}" VerticalOptions="Center" /></Grid>
</ControlTemplate>
</ResourceDictionary>
</ContentPage.Resources>
<toolkit:DockLayout>
<Border toolkit:DockLayout.DockPosition="Top" BackgroundColor="#eee" HeightRequest="80">
<Label HorizontalOptions="Center" Text="Header" VerticalOptions="Center" />
</Border>
<Border toolkit:DockLayout.DockPosition="Bottom" BackgroundColor="#eee">
<Grid ColumnDefinitions="*,*,*,*" HeightRequest="70" HorizontalOptions="Fill">
<Button Command="{Binding PopCommand, Source={Reference fragmentManager}}" HorizontalOptions="Center" Text="Pop" VerticalOptions="Center" />
<Button Grid.Column="1" Command="{Binding PushCommand, Source={Reference fragmentManager}}" CommandParameter="{StaticResource buttonFragment}" HorizontalOptions="Center" Text="Push Button Fragment" VerticalOptions="Center" />
<Button Grid.Column="2" Command="{Binding PushCommand, Source={Reference fragmentManager}}" CommandParameter="{StaticResource entryFragment}" HorizontalOptions="Center" Text="Push Entry Fragment" VerticalOptions="Center" />
<Label Grid.Column="3" HorizontalOptions="Center" Text="{Binding FragmentCount, Source={Reference fragmentManager}, StringFormat='No. of Fragments: {0}'}" VerticalOptions="Center" />
</Grid>
</Border>
<local:FragmentManager x:Name="fragmentManager" InitialFragment="{StaticResource buttonFragment}" />
</toolkit:DockLayout>
</ContentPage>
This example uses: