Files
pos-system/microservices/.agent/skills/maui-enterprise-architect/guidelines/xaml-perf.md
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

12 KiB

XAML Performance / Tối Ưu XAML

Hướng dẫn chi tiết về Compiled Bindings, layout optimization và XAML best practices.

Core Principles / Nguyên Tắc Cốt Lõi

  1. Compiled Bindings: Luôn sử dụng x:DataType để binding tại compile-time
  2. Flat Layouts: Tránh nested layouts, ưu tiên Grid
  3. Virtualization: Sử dụng CollectionView cho danh sách lớn

Compiled Bindings (BẮT BUỘC)

Why Compiled Bindings?

Aspect Reflection Binding Compiled Binding
Performance Slow (runtime lookup) Fast (compile-time)
Error Detection Runtime exceptions Compile-time errors
NativeAOT May not work Full support
Intellisense No Yes

Basic Pattern

<!-- ✅ ĐÚNG: Compiled Bindings -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MyApp.ViewModels"
             xmlns:models="clr-namespace:MyApp.Models"
             x:Class="MyApp.Views.ProductListPage"
             x:DataType="vm:ProductListViewModel">
    
    <!-- Binding được resolve tại compile-time -->
    <Label Text="{Binding Title}" />
    <Label Text="{Binding Products.Count, StringFormat='Total: {0}'}" />
    
    <CollectionView ItemsSource="{Binding Products}">
        <CollectionView.ItemTemplate>
            <!-- x:DataType thay đổi cho ItemTemplate -->
            <DataTemplate x:DataType="models:Product">
                <Grid>
                    <Label Text="{Binding Name}" />
                    <Label Text="{Binding Price, StringFormat='{0:C}'}" />
                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>
<!-- ❌ SAI: Không có x:DataType -->
<ContentPage>
    <!-- Runtime reflection, chậm và không catch lỗi -->
    <Label Text="{Binding Titlee}" /> <!-- Typo không bị phát hiện! -->
</ContentPage>

Nested DataType Changes

<CollectionView ItemsSource="{Binding Categories}"
                x:DataType="vm:CategoryListViewModel">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="models:Category">
            <StackLayout>
                <Label Text="{Binding Name}" />
                
                <!-- Nested collection -->
                <CollectionView ItemsSource="{Binding Products}">
                    <CollectionView.ItemTemplate>
                        <DataTemplate x:DataType="models:Product">
                            <Label Text="{Binding Name}" />
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>
            </StackLayout>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Binding to Parent ViewModel from ItemTemplate

<CollectionView ItemsSource="{Binding Products}"
                x:DataType="vm:ProductListViewModel">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="models:Product">
            <Grid>
                <Label Text="{Binding Name}" />
                
                <!-- Bind to parent ViewModel's command -->
                <Button Text="Select"
                        Command="{Binding Source={RelativeSource AncestorType={x:Type vm:ProductListViewModel}}, Path=SelectProductCommand}"
                        CommandParameter="{Binding .}" />
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Layout Optimization

Grid vs Nested StackLayout

<!-- ❌ SAI: Nested StackLayout (deep visual tree) -->
<StackLayout>
    <StackLayout Orientation="Horizontal">
        <Image Source="{Binding ImageUrl}" />
        <StackLayout>
            <Label Text="{Binding Name}" />
            <Label Text="{Binding Description}" />
            <StackLayout Orientation="Horizontal">
                <Label Text="{Binding Price}" />
                <Button Text="Buy" />
            </StackLayout>
        </StackLayout>
    </StackLayout>
</StackLayout>
<!-- Visual tree depth: 5 levels -->

<!-- ✅ ĐÚNG: Flat Grid -->
<Grid RowDefinitions="Auto,Auto,Auto"
      ColumnDefinitions="80,*,Auto">
    
    <Image Source="{Binding ImageUrl}"
           Grid.RowSpan="3" />
    
    <Label Text="{Binding Name}"
           Grid.Column="1" />
    
    <Label Text="{Binding Description}"
           Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" />
    
    <Label Text="{Binding Price}"
           Grid.Row="2" Grid.Column="1" />
    
    <Button Text="Buy"
            Grid.Row="2" Grid.Column="2" />
</Grid>
<!-- Visual tree depth: 2 levels -->

Layout Performance Tips

<!-- ✅ Fixed sizes when possible -->
<Image WidthRequest="80" HeightRequest="80" Aspect="AspectFill" />

<!-- ✅ Use Grid star sizing -->
<Grid ColumnDefinitions="*,2*,*">
    <!-- Proportional sizing, efficient -->
</Grid>

<!-- ❌ Avoid: AbsoluteLayout for complex UIs -->
<!-- ❌ Avoid: Deep nesting (>4 levels) -->

CollectionView Best Practices

Virtualization (Auto-enabled)

<!-- CollectionView tự động virtualize -->
<CollectionView ItemsSource="{Binding LargeList}"
                ItemsUpdatingScrollMode="KeepItemsInView">
    
    <!-- Fixed item size improves virtualization -->
    <CollectionView.ItemsLayout>
        <LinearItemsLayout Orientation="Vertical"
                          ItemSpacing="8" />
    </CollectionView.ItemsLayout>
    
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="models:Item">
            <!-- Keep template simple for performance -->
            <Grid HeightRequest="60" Padding="10">
                <Label Text="{Binding Name}" />
            </Grid>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

EmptyView và Loading States

<CollectionView ItemsSource="{Binding Products}">
    
    <!-- Empty state -->
    <CollectionView.EmptyView>
        <ContentView>
            <StackLayout VerticalOptions="Center" HorizontalOptions="Center">
                <Image Source="empty_box.svg" WidthRequest="100" />
                <Label Text="No products found" />
            </StackLayout>
        </ContentView>
    </CollectionView.EmptyView>
    
    <CollectionView.ItemTemplate>
        <!-- ... -->
    </CollectionView.ItemTemplate>
</CollectionView>

<!-- Loading overlay -->
<ActivityIndicator IsRunning="{Binding IsLoading}"
                   IsVisible="{Binding IsLoading}"
                   VerticalOptions="Center"
                   HorizontalOptions="Center" />

Resource Dictionary Patterns

App-level Resources

<!-- App.xaml -->
<Application.Resources>
    <ResourceDictionary>
        <!-- Merged dictionaries -->
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
            <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Colors.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    
    <!-- Primary palette -->
    <Color x:Key="Primary">#6750A4</Color>
    <Color x:Key="PrimaryDark">#381E72</Color>
    <Color x:Key="Secondary">#625B71</Color>
    
    <!-- Semantic colors -->
    <Color x:Key="TextPrimary">#1C1B1F</Color>
    <Color x:Key="TextSecondary">#49454F</Color>
    <Color x:Key="Surface">#FFFBFE</Color>
    <Color x:Key="SurfaceVariant">#E7E0EC</Color>
    
    <!-- Status colors -->
    <Color x:Key="Success">#4CAF50</Color>
    <Color x:Key="Warning">#FF9800</Color>
    <Color x:Key="Error">#B3261E</Color>
</ResourceDictionary>

Styles.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    
    <!-- Base styles -->
    <Style TargetType="Label" x:Key="TitleLabel">
        <Setter Property="FontSize" Value="24" />
        <Setter Property="FontFamily" Value="InterBold" />
        <Setter Property="TextColor" Value="{StaticResource TextPrimary}" />
    </Style>
    
    <Style TargetType="Label" x:Key="BodyLabel">
        <Setter Property="FontSize" Value="16" />
        <Setter Property="FontFamily" Value="InterRegular" />
        <Setter Property="TextColor" Value="{StaticResource TextSecondary}" />
    </Style>
    
    <Style TargetType="Button" x:Key="PrimaryButton">
        <Setter Property="BackgroundColor" Value="{StaticResource Primary}" />
        <Setter Property="TextColor" Value="White" />
        <Setter Property="FontFamily" Value="InterSemiBold" />
        <Setter Property="CornerRadius" Value="8" />
        <Setter Property="Padding" Value="16,12" />
    </Style>
    
    <!-- Apply style globally -->
    <Style TargetType="Entry" ApplyToDerivedTypes="True">
        <Setter Property="FontFamily" Value="InterRegular" />
        <Setter Property="FontSize" Value="16" />
    </Style>
</ResourceDictionary>

Usage in Pages

<Label Text="Welcome" Style="{StaticResource TitleLabel}" />
<Label Text="Description" Style="{StaticResource BodyLabel}" />
<Button Text="Submit" Style="{StaticResource PrimaryButton}" />

<!-- Dynamic resource (theme switching) -->
<Label TextColor="{DynamicResource TextPrimary}" />

Hot Reload Tips

<!-- ✅ Hot Reload friendly -->
<Label Text="Static text" />
<Label Text="{Binding DynamicText}" />

<!-- ⚠️ May require restart -->
<!-- Changes to: -->
<!-- - x:DataType -->
<!-- - Namespace imports -->
<!-- - Resource dictionary structure -->

Common Mistakes / Lỗi Thường Gặp

1. Missing x:DataType in ItemTemplate

<!-- ❌ SAI -->
<CollectionView ItemsSource="{Binding Products}" x:DataType="vm:ListViewModel">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <!-- Binding uses reflection, slow! -->
            <Label Text="{Binding Name}" />
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

<!-- ✅ ĐÚNG -->
<DataTemplate x:DataType="models:Product">
    <Label Text="{Binding Name}" />
</DataTemplate>

2. Image Not Caching

<!-- ❌ SAI: Reload mỗi lần -->
<Image Source="{Binding ImageUrl}" />

<!-- ✅ ĐÚNG: Enable caching -->
<Image>
    <Image.Source>
        <UriImageSource Uri="{Binding ImageUrl}"
                        CachingEnabled="True"
                        CacheValidity="1:00:00:00" />
    </Image.Source>
</Image>

3. Heavy Work in Converters

// ❌ SAI: Slow converter
public object Convert(object value, ...)
{
    // Heavy computation on UI thread!
    return ProcessData(value);
}

// ✅ ĐÚNG: Pre-compute in ViewModel
[ObservableProperty]
private string _processedValue; // Already computed

Performance Checklist

  • x:DataType declared on every page and DataTemplate
  • No nested StackLayout deeper than 2 levels
  • CollectionView used for lists (not StackLayout)
  • Fixed sizes where possible (WidthRequest, HeightRequest)
  • Image caching enabled for remote images
  • Styles in ResourceDictionary, not inline
  • StaticResource over DynamicResource when possible

Resources