Search code examples
c#.netmaui

Does MAUI have a concept similar to Android’s Fragments


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?


Solution

  • 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:

    • CommunityToolkit.Mvvm for the [RelayCommand] attribute
    • CommunityToolkit.Maui for the DockLayout component