Skip to content

Commit

Permalink
Resolve some life cycle issues with TabbedPage when nested inside a…
Browse files Browse the repository at this point in the history
… `NavigationPage` (#11530)

* Fix fragment manager life cycle with TabbbedPage

* - add additional tests

* - add more tests and fix a couple more found issues

* - remove testing code

* - fix up tabbedpage gallery

* - fix up tests

* - fix up fragment management

* - cleanup

* - add additional tests

* - add delay

* Update StackNavigationManager.cs

* Update TabbedPageTests.cs

* Update ControlsHandlerTestBase.cs

* - remove disable nullable

* - add comment fix traversal

* - cleanup Destory checking code and add gallery tests

* - cleanup handling of adapter key

* - comment out ios

* - fix scenarios where navpage gets disconnected mid navigation

* Update MultiPageFragmentStateAdapter.cs
  • Loading branch information
PureWeen committed Feb 6, 2023
1 parent 3841372 commit f73f809
Show file tree
Hide file tree
Showing 33 changed files with 1,091 additions and 353 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ protected override void Dispose(bool disposing)

if (disposing)
{
if (_currentFragment != null && !FragmentManager.IsDestroyed)
if (_currentFragment != null && !FragmentManager.IsDestroyed(Context))
{
FragmentTransaction transaction = FragmentManager.BeginTransactionEx();
transaction.RemoveEx(_currentFragment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ protected override void Dispose(bool disposing)

_page = null;

if (!_fragmentManager.IsDestroyed)
if (!_fragmentManager.IsDestroyed(_page?.Handler?.MauiContext?.Context))
{
FragmentTransaction transaction = _fragmentManager.BeginTransactionEx();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,9 @@
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Pages.TabbedPageGallery"
xmlns:views="clr-namespace:Maui.Controls.Sample.Pages"
Title="Tabbed Page">
<ContentPage Title="Tab 1">
<VerticalStackLayout>
<Button Text="Set Tabbed Page as Root" Clicked="OnTabbedPageAsRoot"></Button>
<Button Text="Toggle Bottom Tabs (Android)" Clicked="OnSetToBottomTabs"></Button>
<Button Text="Change Tab Index" Clicked="OnChangeTabIndex"></Button>
<Button Text="Toggle TabBar Background Color" Clicked="OnToggleTabBar"></Button>
<Button Text="Toggle TabBar Text Color" Clicked="OnToggleTabBarTextColor"></Button>
<Button Text="Toggle Tab Item Selected" Clicked="OnToggleTabItemSelectedColor"></Button>
<Button Text="Toggle Tab Item UnselectedColor" Clicked="OnToggleTabItemUnSelectedColor"></Button>
</VerticalStackLayout>
</ContentPage>

<views:TabbedPageGalleryMainPage></views:TabbedPageGalleryMainPage>

</TabbedPage>
Original file line number Diff line number Diff line change
Expand Up @@ -14,77 +14,5 @@ public TabbedPageGallery()
this.Children.Add(new NavigationGallery());
this.Children.Add(new NavigationPage(new NavigationGallery()) { Title = "With Nav Page" });
}

void OnTabbedPageAsRoot(object sender, EventArgs e)
{
var topTabs =
new TabbedPage()
{
Children =
{
Handler.MauiContext.Services.GetRequiredService<Page>(),
new NavigationPage(new Pages.NavigationGallery()) { Title = "Navigation Gallery" }
}
};

this.Handler?.DisconnectHandler();
Application.Current.MainPage?.Handler?.DisconnectHandler();
Application.Current.MainPage = topTabs;
}

void OnSetToBottomTabs(object sender, EventArgs e)
{
var bottomTabs = new TabbedPage()
{
Children =
{
Handler.MauiContext.Services.GetRequiredService<Page>(),
new NavigationPage(new Pages.NavigationGallery()) { Title = "Navigation Gallery" }
}
};

this.Handler?.DisconnectHandler();
Application.Current.MainPage?.Handler?.DisconnectHandler();

AndroidSpecific.TabbedPage.SetToolbarPlacement(bottomTabs, AndroidSpecific.ToolbarPlacement.Bottom);
Application.Current.MainPage = bottomTabs;
}

void OnChangeTabIndex(object sender, EventArgs e)
{
CurrentPage = Children[1];
}

void OnToggleTabBar(object sender, EventArgs e)
{
if ((this.BarBackground as SolidColorBrush)?.Color == SolidColorBrush.Purple.Color)
this.BarBackground = null;
else
this.BarBackground = SolidColorBrush.Purple;
}

void OnToggleTabBarTextColor(object sender, EventArgs e)
{
if (this.BarTextColor == Colors.Green)
this.BarTextColor = null;
else
this.BarTextColor = Colors.Green;
}

void OnToggleTabItemUnSelectedColor(object sender, EventArgs e)
{
if (this.UnselectedTabColor == Colors.Blue)
this.UnselectedTabColor = null;
else
this.UnselectedTabColor = Colors.Blue;
}

void OnToggleTabItemSelectedColor(object sender, EventArgs e)
{
if (this.SelectedTabColor == Colors.Pink)
this.SelectedTabColor = null;
else
this.SelectedTabColor = Colors.Pink;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage Title="Tab 1"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Pages.TabbedPageGalleryMainPage">
<ScrollView>
<VerticalStackLayout>
<Button Text="Set Tabbed Page as Root" Clicked="OnTabbedPageAsRoot"></Button>
<Button Text="Toggle Bottom Tabs (Android)" Clicked="OnSetToBottomTabs"></Button>
<Button Text="Change Tab Index" Clicked="OnChangeTabIndex"></Button>
<Button Text="Toggle TabBar Background Color" Clicked="OnToggleTabBar"></Button>
<Button Text="Toggle TabBar Text Color" Clicked="OnToggleTabBarTextColor"></Button>
<Button Text="Toggle Tab Item Selected" Clicked="OnToggleTabItemSelectedColor"></Button>
<Button Text="Toggle Tab Item UnselectedColor" Clicked="OnToggleTabItemUnSelectedColor"></Button>
<Button Text="Add Tab" Clicked="OnAddTab"></Button>
<Button Text="Remove Tab" Clicked="OnRemoveTab"></Button>
<Button Text="Remove All New Tabs" Clicked="OnRemoveAllTabs"></Button>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using AndroidSpecific = Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;

namespace Maui.Controls.Sample.Pages
{
public partial class TabbedPageGalleryMainPage
{
public TabbedPageGalleryMainPage()
{
InitializeComponent();
}

TabbedPage _tabbedPage;
TabbedPage GetTabbedPage() => _tabbedPage ??= (TabbedPage)Parent;

void SetNewMainPage(Page page)
{
Application.Current.Windows[0].Page = page;
}

void OnTabbedPageAsRoot(object sender, EventArgs e)
{
var topTabs =
new TabbedPage()
{
Children =
{
Handler.MauiContext.Services.GetRequiredService<Page>(),
new NavigationPage(new Pages.NavigationGallery()) { Title = "Navigation Gallery" }
}
};

SetNewMainPage(topTabs);
}

void OnSetToBottomTabs(object sender, EventArgs e)
{
var bottomTabs = new TabbedPage()
{
Children =
{
Handler.MauiContext.Services.GetRequiredService<Page>(),
new NavigationPage(new Pages.NavigationGallery()) { Title = "Navigation Gallery" }
}
};

SetNewMainPage(bottomTabs);
AndroidSpecific.TabbedPage.SetToolbarPlacement(bottomTabs, AndroidSpecific.ToolbarPlacement.Bottom);
Application.Current.MainPage = bottomTabs;
}

void OnChangeTabIndex(object sender, EventArgs e)
{
GetTabbedPage().CurrentPage = GetTabbedPage().Children[1];
}

void OnToggleTabBar(object sender, EventArgs e)
{
if ((GetTabbedPage().BarBackground as SolidColorBrush)?.Color == SolidColorBrush.Purple.Color)
GetTabbedPage().BarBackground = null;
else
GetTabbedPage().BarBackground = SolidColorBrush.Purple;
}

void OnToggleTabBarTextColor(object sender, EventArgs e)
{
if (GetTabbedPage().BarTextColor == Colors.Green)
GetTabbedPage().BarTextColor = null;
else
GetTabbedPage().BarTextColor = Colors.Green;
}

void OnToggleTabItemUnSelectedColor(object sender, EventArgs e)
{
if (GetTabbedPage().UnselectedTabColor == Colors.Blue)
GetTabbedPage().UnselectedTabColor = null;
else
GetTabbedPage().UnselectedTabColor = Colors.Blue;
}

void OnToggleTabItemSelectedColor(object sender, EventArgs e)
{
if (GetTabbedPage().SelectedTabColor == Colors.Pink)
GetTabbedPage().SelectedTabColor = null;
else
GetTabbedPage().SelectedTabColor = Colors.Pink;
}

void OnRemoveTab(object sender, EventArgs e)
{
if (GetTabbedPage().Children.LastOrDefault() is TabbedPageGalleryMainPage mainPage)
{
GetTabbedPage().Children.Remove(mainPage);
}
}

void OnRemoveAllTabs(object sender, EventArgs e)
{
while (GetTabbedPage().Children.LastOrDefault() is TabbedPageGalleryMainPage mainPage)
{
GetTabbedPage().Children.Remove(mainPage);
}
}

void OnAddTab(object sender, EventArgs e)
{
GetTabbedPage()
.Children
.Add(new TabbedPageGalleryMainPage() { Title = $"Tab {GetTabbedPage().Children.Count}" });
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,9 @@ public ShellFragmentContainer(ShellContent shellContent, IMauiContext mauiContex
public override AView OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
_page = ((IShellContentController)ShellContentTab).GetOrCreateContent();
_page.ToPlatform(_mauiContext, RequireContext(), inflater, ChildFragmentManager);

IMauiContext mauiContext = null;

// If the page has already been created with a handler then we just let it retain the same
// Handler and MauiContext
// But we want to update the inflater and ChildFragmentManager to match
// the handlers new home
if (_page.Handler?.MauiContext is MauiContext scopedMauiContext)
{
// If this page comes to us from a different activity then don't reuse it
// disconnect the handler so it can recreate against new MauiContext
if (scopedMauiContext.GetActivity() == Context.GetActivity())
{
scopedMauiContext.AddWeakSpecific(ChildFragmentManager);
scopedMauiContext.AddWeakSpecific(inflater);
mauiContext = scopedMauiContext;
}
else
{
_page.Handler.DisconnectHandler();
}
}

mauiContext ??= _mauiContext.MakeScoped(layoutInflater: inflater, fragmentManager: ChildFragmentManager);

return new ShellPageContainer(RequireContext(), (IPlatformViewHandler)_page.ToHandler(mauiContext), true)
return new ShellPageContainer(RequireContext(), (IPlatformViewHandler)_page.Handler, true)
{
LayoutParameters = new LP(LP.MatchParent, LP.MatchParent)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,32 +181,36 @@ async Task SendHandlerUpdateAsync(
// Wait for pending navigation tasks to finish
await SemaphoreSlim.WaitAsync();

var currentNavRequestTaskSource = new TaskCompletionSource<object>();
_allPendingNavigationCompletionSource ??= new TaskCompletionSource<object>();

if (CurrentNavigationTask == null)
{
CurrentNavigationTask = _allPendingNavigationCompletionSource.Task;
}
else if (CurrentNavigationTask != _allPendingNavigationCompletionSource.Task)
// If our handler was removed while waiting then don't do anything
if (Handler != null)
{
throw new InvalidOperationException("Pending Navigations still processing");
}
var currentNavRequestTaskSource = new TaskCompletionSource<object>();
_allPendingNavigationCompletionSource ??= new TaskCompletionSource<object>();

_currentNavigationCompletionSource = currentNavRequestTaskSource;
if (CurrentNavigationTask == null)
{
CurrentNavigationTask = _allPendingNavigationCompletionSource.Task;
}
else if (CurrentNavigationTask != _allPendingNavigationCompletionSource.Task)
{
throw new InvalidOperationException("Pending Navigations still processing");
}

_currentNavigationCompletionSource = currentNavRequestTaskSource;

// We create a new list to send to the handler because the structure backing
// The Navigation stack isn't immutable
var immutableNavigationStack = new List<IView>(NavigationStack);
firePostNavigatingEvents?.Invoke();
// We create a new list to send to the handler because the structure backing
// The Navigation stack isn't immutable
var immutableNavigationStack = new List<IView>(NavigationStack);
firePostNavigatingEvents?.Invoke();

// Create the request for the handler
var request = new NavigationRequest(immutableNavigationStack, animated);
((IStackNavigation)this).RequestNavigation(request);
// Create the request for the handler
var request = new NavigationRequest(immutableNavigationStack, animated);
((IStackNavigation)this).RequestNavigation(request);

// Wait for the handler to finish processing the navigation
// This task completes once the handler calls INavigationView.Finished
await currentNavRequestTaskSource.Task;
// Wait for the handler to finish processing the navigation
// This task completes once the handler calls INavigationView.Finished
await currentNavRequestTaskSource.Task;
}
}
finally
{
Expand Down Expand Up @@ -245,6 +249,13 @@ private protected override void OnHandlerChangedCore()
})
.FireAndForget(Handler);
}

// If the handler is disconnected and we're still waiting for updates from the handler
// Just complete any waits
if (Handler == null && _waitingCount > 0)
{
((IStackNavigation)this).NavigationFinished(this.NavigationStack);
}
}

// Once we get all platforms over to the new APIs
Expand Down
Loading

0 comments on commit f73f809

Please sign in to comment.