CommunityToolkit / Maui

The .NET MAUI Community Toolkit is a community-created library that contains .NET MAUI Extensions, Advanced UI/UX Controls, and Behaviors to help make your life as a .NET MAUI developer easier
https://learn.microsoft.com/dotnet/communitytoolkit/maui
MIT License
2.27k stars 396 forks source link

[BUG] Expander does not load contents dynamically as it did in Forms #1598

Closed KRA2008 closed 8 months ago

KRA2008 commented 11 months ago

Is there an existing issue for this?

Did you read the "Reporting a bug" section on Contributing file?

Current Behavior

Expander in MAUI does not seem to inflate its content on demand as Expander in Forms did.

Expected Behavior

Large/complex layouts set as the Content property of Expanders should not slow the inflation of a Page containing those Expanders.

Steps To Reproduce

  1. Make a new MAUI app and add CommunityToolkit to it
  2. Add a new page and put an Expander on it
  3. Give that Expander a complex/large layout as its Content, for example:
    <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="10"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="50"/>
        <ColumnDefinition Width="65"/>
        <ColumnDefinition Width="10"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="50"/>
        <RowDefinition Height="60"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    {{Fill this up with Labels, Switches, and Images}}
    </Grid>
  4. Consider copy-pasting a few identical Expanders to accentuate the effect
  5. Make it possible to Push navigate from the MainPage to the Expander page
  6. Run the app and navigate to the Expander page, noting how long it takes to navigate there
  7. Comment out/remove the Contents of the Expanders, and repeat the previous step

Expected behavior: The app should navigate to the Expander page quickly, regardless of the contents of the Expanders.

Actual behavior: The app bogs down severely when multiple complex Expanders are on the page.

Link to public reproduction project repository

https://github.com/KRA2008/MAUIExpanderIsSlow

Environment

- .NET MAUI CommunityToolkit: 7.0.0
- OS: Windows 10 Home 19045.3693
- .NET MAUI: 8.0.3

Anything else?

When migrating from Forms to MAUI I found that the ContentTemplate property of the Expander was removed. I was confused by this but it's easy enough to just remove. I was hoping this meant that the dynamic inflation was just the default behavior but this either doesn't appear to be working or is not part of the intended design. I suspect I may be able to emulate the dynamic inflation by combining LazyView and Expander but I don't want to move on that unless this is the final intended state of the Expander.

pictos commented 11 months ago

@KRA2008 how are you doing your perf test? I tried your sample and for me it looks like pretty fast, even in Debug mode (where things are slow by default), you can see on my video below:

https://github.com/CommunityToolkit/Maui/assets/20712372/110de853-447f-40f0-aca6-a373b018cbb5

KRA2008 commented 11 months ago

@pictos You're not doing the problematic action. You're opening and closing the Expander, I'm saying the problem is in the inflation of the whole page containing the Expander. The Expander is to blame during the inflation because it's supposed to make the page inflate quickly by only inflating its own contents dynamically when opened. Please reword my title or whatever as needed to clarify.

pictos commented 11 months ago

@KRA2008 can you provide a video showing what you want to show? It's hard to guess and try to follow the steps. If possible and you have profile data that we can reference that would be amazing as well.

KRA2008 commented 11 months ago

https://github.com/CommunityToolkit/Maui/assets/5667342/46a6352c-c3ff-48e6-af20-cf6172122693

KRA2008 commented 11 months ago

i have no idea how to profile, i checked out the wiki on it and my head began spinning after already spending over an hour trying to get you the video.

just a note, the video does show it running in debug mode, and it is definitely faster in release mode, but there is still a noticeable difference. my understanding is that if the contents are really not inflated until the expander is tapped there should be no difference between a page with simple expanders and a page with complex expanders.

KRA2008 commented 11 months ago

i also pushed an update to the repo to match the state shown in the video.

KRA2008 commented 10 months ago

for some reason i cannot figure out how to use LazyView, so the workaround i mentioned where a LazyView is used as the contents of an Expander is not coming from me.

cat0363 commented 10 months ago

It seems to be improved by using LazyView. Below is the verification video.

https://github.com/CommunityToolkit/Maui/assets/125236133/b8d5f1ce-4071-4239-8e0a-3d24338b5624

[Sample] https://github.com/CommunityToolkit/Maui/tree/main/samples/CommunityToolkit.Maui.Sample/Pages/Views/LazyView [Document] https://learn.microsoft.com/en-us/dotnet/communitytoolkit/maui/views/lazyview

KRA2008 commented 10 months ago

@cat0363 that looks great. can you share your implementation, please? you could push it to my recreation repo.

cat0363 commented 10 months ago

@KRA2008 , Below is the sample code I created.

https://github.com/cat0363/MauiComm-ExpanderAndLazyView.git

MainPage.xaml in the sample code above corresponds to ExpanderPageComplex.xaml.

[MainPage.xaml]

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:local="clr-namespace:MauiComm_ExpanderAndLazyView"
             x:Class="MauiComm_ExpanderAndLazyView.MainPage">
    <ScrollView>
        <VerticalStackLayout>
            <toolkit:Expander ExpandedChanged="Expander_ExpandedChanged">
                <toolkit:Expander.Header>
                    <Label Text="Complex Expander 1"/>
                </toolkit:Expander.Header>
                <toolkit:Expander.Content>
                    <local:LazyViewExpanderContent />
                </toolkit:Expander.Content>
            </toolkit:Expander>
            <toolkit:Expander ExpandedChanged="Expander_ExpandedChanged">
                <toolkit:Expander.Header>
                    <Label Text="Complex Expander 2"/>
                </toolkit:Expander.Header>
                <toolkit:Expander.Content>
                    <local:LazyViewExpanderContent />
                </toolkit:Expander.Content>
            </toolkit:Expander>
            <toolkit:Expander ExpandedChanged="Expander_ExpandedChanged">
                <toolkit:Expander.Header>
                    <Label Text="Complex Expander 3"/>
                </toolkit:Expander.Header>
                <toolkit:Expander.Content>
                    <local:LazyViewExpanderContent />
                </toolkit:Expander.Content>
            </toolkit:Expander>
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

Expander.Content is created as ExpanderContent that inherits ContentView. This is the same as the contents of Expander.Content in ExpanderPageComplex.xaml.

[ExpanderContent.xaml]

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiComm_ExpanderAndLazyView.ExpanderContent">
    <Grid ColumnSpacing="0"
          RowSpacing="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="65"/>
            <ColumnDefinition Width="10"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="60"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Frame Grid.Row="0"
               Grid.Column="1"
               Grid.ColumnSpan="2"
               HorizontalOptions="FillAndExpand">
            <Picker/>
        </Frame>
        <Image Grid.Row="0"
               Grid.Column="3" 
               Source="help">
            <Image.GestureRecognizers>
                <TapGestureRecognizer/>
            </Image.GestureRecognizers>
        </Image>

        <Label Grid.Row="1"
               Grid.Column="1"
               Text="Capture using single-shot mirror division method"/>
        <Image Grid.Row="1"
               Grid.Column="2" 
               Source="help">
            <Image.GestureRecognizers>
                <TapGestureRecognizer/>
            </Image.GestureRecognizers>
        </Image>
        <Switch Grid.Row="1"
                Grid.Column="3"/>

        <Label Grid.Row="2"
               Grid.Column="1"
               Grid.ColumnSpan="2"
               Text="Immersive final viewing"/>
        <Switch Grid.Row="2"
                Grid.Column="3"/>

        <Label Grid.Row="3"
               Grid.Column="1" 
               Grid.ColumnSpan="2"
               Text="Cardboard separation distance"/>
        <Frame Grid.Row="3" 
               Grid.Column="3">
            <Entry/>
        </Frame>

        <Label Grid.Row="4"
               Grid.Column="1"
               Grid.ColumnSpan="2"
               Text="Add lens correction/barrel distortion (may lower frame rate)"/>
        <Switch Grid.Row="4" 
                Grid.Column="3" />

        <Label Grid.Row="5"
               Grid.Column="1" 
               Grid.ColumnSpan="2"
               Text="Add lens correction/barrel distortion to final images only (may slow down final display but will speed up capturing)"
               IsVisible="False">
        </Label>
        <Switch Grid.Row="5"
                Grid.Column="3" 
                IsVisible="False">
        </Switch>

        <Label Grid.Row="6"
               Grid.Column="1" 
               Grid.ColumnSpan="2"
               Text="Lens correction strength"
               IsVisible="False">
        </Label>
        <Frame Grid.Row="6"
               Grid.Column="3" 
               IsVisible="False">
            <Entry/>
        </Frame>

        <Label Grid.Row="7"
               Grid.Column="1"
               Grid.ColumnSpan="2"
               Text="Downsize image (may increase frame rate)"
               IsVisible="False">
        </Label>
        <Switch Grid.Row="7"
                Grid.Column="3"
                IsVisible="False">
        </Switch>

        <Label Grid.Row="8"
               Grid.Column="1"
               Grid.ColumnSpan="2"
               Text="Downsize image percentage"
               IsVisible="False">
        </Label>
        <Frame Grid.Row="8"
               Grid.Column="3"
               IsVisible="False">
            <Entry/>
        </Frame>

        <Button Grid.Row="9"
                Grid.Column="1"
                Grid.ColumnSpan="3" 
                Text="Reset to Defaults"/>

        <Label Grid.Row="2"
               Grid.Column="1" 
               Grid.ColumnSpan="2"
               Text="Maximum parallel view image width (points)"
               />
        <Frame Grid.Row="2" Grid.Column="3">
            <Entry/>
        </Frame>
    </Grid>
</ContentView>

Next, create LazyViewExpanderContent that inherits LazyView. Here, the ExpanderContent from earlier is specified as LazyView.

[LazyViewExpanderContent.cs]

public class LazyViewExpanderContent : LazyView<ExpanderContent>
{
    public override async ValueTask LoadViewAsync(CancellationToken token)
    {
        await base.LoadViewAsync(token);
    }
}

Finally, write the process to load ExpandContent in Expander's ExpandedChanged event. If the load does not complete within 5 seconds, the load will be canceled.

[MainPage.xaml.cs]

private async void Expander_ExpandedChanged(object sender, CommunityToolkit.Maui.Core.ExpandedChangedEventArgs e)
{
    var expander = (Expander)sender;
    if (expander.Content is not null)
    {
        var lazyView = (LazyViewExpanderContent)expander.Content;
        if (expander.IsExpanded)
        {
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
            await lazyView.LoadViewAsync(cts.Token);
        }
    }
}

Please compare it with your code.

KRA2008 commented 10 months ago

@cat0363 excellent, thank you.

VladislavAntonyuk commented 8 months ago

As the workaround is found, I believe we can close it. Feel free to reopen the issue

Strypper commented 7 months ago

Impressive how to we pass data to the <local:LazyViewExpanderContent />?