diff options
4 files changed, 122 insertions, 19 deletions
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/PopAfterRemove.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/PopAfterRemove.cs new file mode 100644 index 00000000..c7508fbd --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/PopAfterRemove.cs @@ -0,0 +1,86 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.Navigation)] +#endif + + [Preserve(AllMembers = true)] + [Issue(IssueTracker.None, 0101100101, + "PopAsync crashing after RemovePage when support packages are updated to 25.1.1", + PlatformAffected.Android)] + public class PopAfterRemove : TestNavigationPage + { + ContentPage _intermediate1; + ContentPage _intermediate2; + + protected override async void Init() + { + _intermediate1 = Intermediate(); + _intermediate2 = Intermediate(); + + await PushAsync(Root()); + await PushAsync(_intermediate1); + await PushAsync(_intermediate2); + await PushAsync(Last()); + } + + const string StartTest = "Start Test"; + const string RootLabel = "Root"; + + ContentPage Last() + { + var test = new Button { Text = StartTest }; + + var instructions = new Label + { + Text = + $"Tap the button labeled '{StartTest}'. The app should navigate to a page displaying the label " + + $"'{RootLabel}'. If the application crashes, the test has failed." + }; + + var layout = new StackLayout(); + + layout.Children.Add(instructions); + layout.Children.Add(test); + + test.Clicked += (sender, args) => + { + Navigation.RemovePage(_intermediate2); + Navigation.RemovePage(_intermediate1); + + Navigation.PopAsync(true); + }; + + return new ContentPage { Content = layout }; + } + + static ContentPage Root() + { + return new ContentPage { Content = new Label { Text = RootLabel } }; + } + + static ContentPage Intermediate() + { + return new ContentPage { Content = new Label { Text = "Page" } }; + } + +#if UITEST + [Test] + public void PopAsyncAfterRemovePageDoesNotCrash() + { + RunningApp.WaitForElement(StartTest); + RunningApp.Tap(StartTest); + RunningApp.WaitForElement(RootLabel); + } +#endif + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index b4155f62..04954748 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -271,6 +271,7 @@ <DependentUpon>PlatformSpecifics_iOSTranslucentNavBarX.xaml</DependentUpon> <SubType>Code</SubType> </Compile> + <Compile Include="$(MSBuildThisFileDirectory)PopAfterRemove.cs" /> <Compile Include="$(MSBuildThisFileDirectory)TestPages\ScreenshotConditionalApp.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Bugzilla41842.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Bugzilla42277.cs" /> diff --git a/Xamarin.Forms.Core.UITests.Shared/UITestCategories.cs b/Xamarin.Forms.Core.UITests.Shared/UITestCategories.cs index cca59b5a..2b2ff800 100644 --- a/Xamarin.Forms.Core.UITests.Shared/UITestCategories.cs +++ b/Xamarin.Forms.Core.UITests.Shared/UITestCategories.cs @@ -33,6 +33,7 @@ public const string InputTransparent = "InputTransparent"; public const string IsEnabled = "IsEnabled"; public const string Gestures = "Gestures"; + public const string Navigation = "Navigation"; public const string ManualReview = "ManualReview"; } diff --git a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs index eb2da7a0..bd96bf31 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs @@ -79,7 +79,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = ((FormsAppCompatActivity)Context).SupportFragmentManager); - IPageController PageController => Element as IPageController; + IPageController PageController => Element; bool ToolbarVisible { @@ -437,11 +437,6 @@ namespace Xamarin.Forms.Platform.Android.AppCompat ResetToolbar(); } - void FilterPageFragment(Page page) - { - _fragmentStack.RemoveAll(f => ((FragmentContainer)f).Page == page); - } - void HandleToolbarItemPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == MenuItem.IsEnabledProperty.PropertyName || e.PropertyName == MenuItem.TextProperty.PropertyName || e.PropertyName == MenuItem.IconProperty.PropertyName) @@ -552,30 +547,50 @@ namespace Xamarin.Forms.Platform.Android.AppCompat _drawerToggle.DrawerIndicatorEnabled = true; } + Fragment GetPageFragment(Page page) + { + for (int n = 0; n < _fragmentStack.Count; n++) + { + if ((_fragmentStack[n] as FragmentContainer)?.Page == page) + { + return _fragmentStack[n]; + } + } + + return null; + } + void RemovePage(Page page) { + Fragment fragment = GetPageFragment(page); + + if (fragment == null) + { + return; + } + + // Go ahead and take care of the fragment bookkeeping for the page being removed + FragmentTransaction transaction = FragmentManager.BeginTransaction(); + transaction.DisallowAddToBackStack(); + transaction.Remove(fragment); + transaction.CommitAllowingStateLoss(); + FragmentManager.ExecutePendingTransactions(); + + // And remove the fragment from our own stack + _fragmentStack.Remove(fragment); + + // Now handle all the XF removal/cleanup IVisualElementRenderer rendererToRemove = Android.Platform.GetRenderer(page); if (rendererToRemove != null) { var containerToRemove = (PageContainer)rendererToRemove.View.Parent; - rendererToRemove.View?.RemoveFromParent(); - rendererToRemove?.Dispose(); - - // This causes a NullPointerException in API 25.1+ when we later call ExecutePendingTransactions(); - // We may want to remove this in the future if it is resolved in the Android SDK. - if ((int)Build.VERSION.SdkInt < 25) - { - containerToRemove?.RemoveFromParent(); - containerToRemove?.Dispose(); - } + containerToRemove?.RemoveFromParent(); + containerToRemove?.Dispose(); } - // Also remove this page from the fragmentStack - FilterPageFragment(page); - Device.StartTimer(TimeSpan.FromMilliseconds(10), () => { UpdateToolbar(); |