Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve some life cycle issues with TabbedPage when nested inside a NavigationPage #11530

Merged
merged 20 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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