From 2aeeece793a2b79f6e4dd1af0533fce8b23e376b Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Tue, 8 Oct 2019 12:16:03 -0600 Subject: [PATCH] Shell android dispose (#7768) * Better dispose of Android shell renderers * - fixup a few dispose paths * - set page to null * - header container --- .../Issue6640.cs | 95 ++++++++++++++ .../Issue6738.cs | 9 +- .../TestPages/TestPages.cs | 11 +- .../Xamarin.Forms.Controls.Issues.Shared.projitems | 1 + Xamarin.Forms.Core/Shell/ShellSection.cs | 1 - .../AppCompat/ShellFragmentContainer.cs | 7 +- .../Extensions/FragmentManagerExtensions.cs | 5 + .../Renderers/ColorChangeRevealDrawable.cs | 47 ++++++- .../ShellBottomNavViewAppearanceTracker.cs | 74 ++++++----- .../Renderers/ShellContentFragment.cs | 55 ++++++-- .../Renderers/ShellFlyoutRecyclerAdapter.cs | 62 ++++++++- .../Renderers/ShellFlyoutRenderer.cs | 24 ++++ .../ShellFlyoutTemplatedContentRenderer.cs | 142 +++++++++++---------- .../Renderers/ShellFragmentPagerAdapter.cs | 13 +- .../Renderers/ShellItemRenderer.cs | 70 +++++++--- .../Renderers/ShellItemRendererBase.cs | 58 ++++++--- .../Renderers/ShellRenderer.cs | 51 +++++--- .../Renderers/ShellSearchView.cs | 11 +- .../Renderers/ShellSearchViewAdapter.cs | 10 +- .../Renderers/ShellSectionRenderer.cs | 30 +++-- .../Renderers/ShellTabLayoutAppearanceTracker.cs | 15 +-- .../Renderers/ShellToolbarAppearanceTracker.cs | 11 +- .../Renderers/ShellToolbarTracker.cs | 15 +-- 23 files changed, 582 insertions(+), 235 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6640.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6640.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6640.cs new file mode 100644 index 0000000..858badb --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6640.cs @@ -0,0 +1,95 @@ +using System; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 6640, "[Android] Crash when app go background", PlatformAffected.Android)] + public class Issue6640 : TestContentPage + { + protected override void Init() + { + Content = new StackLayout + { + Children = + { + new Button + { + AutomationId = "GoToShell", + Text = "Click the button", + Command = new Command(() => + { + Application.Current.MainPage = new MasterPageShell(); + }) + } + } + }; + } + + [Preserve(AllMembers = true)] + public class MasterPageShell : TestShell + { + protected override void Init() + { + FlyoutHeader = new FlyoutHeader(); + Items.Add(new FlyoutItem + { + Title = "Issue 6640", + Items = + { + new ShellSection + { + Items = + { + new ShellContent + { + Content = new ContentPage + { + Content = new StackLayout + { + Children = + { + new Button + { + AutomationId = "Logout", + Text = "Click the button", + Command = new Command(() => + { + Application.Current.MainPage = new ContentPage + { + Content = new StackLayout + { + BackgroundColor = Color.White, + Children = + { + new Label + { + Text = $"Press Back Arrow to send application to background and wait few seconds.{Environment.NewLine}Application must not crash." + } + } + } + }; + }) + } + } + } + } + } + } + } + } + }); + } + } + + [Preserve(AllMembers = true)] + public class FlyoutHeader : TestContentPage + { + protected override void Init() + { + Content = new Grid(); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6738.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6738.cs index 0af2f95..0f3bcab 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6738.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6738.cs @@ -71,7 +71,7 @@ namespace Xamarin.Forms.Controls.Issues returnButton.Clicked += OnReturnTapped; stackLayout.Children.Add(returnButton); - var tabTwoPage = new ContentPage { Content = stackLayout }; + var tabTwoPage = new ContentPage { Content = stackLayout }; tabOne.Items.Add(tabOnePage); tabTwo.Items.Add(tabTwoPage); @@ -86,7 +86,8 @@ namespace Xamarin.Forms.Controls.Issues Items = { tabOne, tabTwo } } ); - Items.Add(new FlyoutItem { + Items.Add(new FlyoutItem + { Title = flyoutOtherTitle, Items = { flyoutContent } }); @@ -104,8 +105,8 @@ namespace Xamarin.Forms.Controls.Issues RunningApp.WaitForElement(insertAutomationId); RunningApp.Tap(insertAutomationId); - TapInFlyout(flyoutOtherTitle); - TapInFlyout(flyoutMainTitle); + TapInFlyout(flyoutOtherTitle, timeoutMessage: flyoutOtherTitle); + TapInFlyout(flyoutMainTitle, timeoutMessage: flyoutMainTitle); RunningApp.WaitForElement(returnAutomationId); RunningApp.Tap(returnAutomationId); diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs index 4bb24f4..708c985 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs @@ -728,10 +728,10 @@ namespace Xamarin.Forms.Controls AppSetup.EndIsolate(); } } - public void ShowFlyout(string flyoutIcon = FlyoutIconAutomationId, bool usingSwipe = false, bool testForFlyoutIcon = true) + public void ShowFlyout(string flyoutIcon = FlyoutIconAutomationId, bool usingSwipe = false, bool testForFlyoutIcon = true, string timeoutMessage = null) { if(testForFlyoutIcon) - RunningApp.WaitForElement(flyoutIcon); + RunningApp.WaitForElement(flyoutIcon, timeoutMessage); if (usingSwipe) { @@ -745,10 +745,11 @@ namespace Xamarin.Forms.Controls } - public void TapInFlyout(string text, string flyoutIcon = FlyoutIconAutomationId, bool usingSwipe = false) + public void TapInFlyout(string text, string flyoutIcon = FlyoutIconAutomationId, bool usingSwipe = false, string timeoutMessage = null) { - ShowFlyout(flyoutIcon, usingSwipe); - RunningApp.WaitForElement(text); + timeoutMessage = timeoutMessage ?? text; + ShowFlyout(flyoutIcon, usingSwipe, timeoutMessage: timeoutMessage); + RunningApp.WaitForElement(text, timeoutMessage); RunningApp.Tap(text); } 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 5425d21..e51a2a8 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 @@ -13,6 +13,7 @@ + diff --git a/Xamarin.Forms.Core/Shell/ShellSection.cs b/Xamarin.Forms.Core/Shell/ShellSection.cs index d3c5027..ad96c0b 100644 --- a/Xamarin.Forms.Core/Shell/ShellSection.cs +++ b/Xamarin.Forms.Core/Shell/ShellSection.cs @@ -352,7 +352,6 @@ namespace Xamarin.Forms _navStack.Insert(index, page); AddPage(page); - SendAppearanceChanged(); var args = new NavigationRequestedEventArgs(page, before, false) { diff --git a/Xamarin.Forms.Platform.Android/AppCompat/ShellFragmentContainer.cs b/Xamarin.Forms.Platform.Android/AppCompat/ShellFragmentContainer.cs index d77643c..e5c47d4 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/ShellFragmentContainer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/ShellFragmentContainer.cs @@ -4,6 +4,7 @@ using Android.Runtime; using Android.Views; using System; using LP = Android.Views.ViewGroup.LayoutParams; +using AView = Android.Views.View; namespace Xamarin.Forms.Platform.Android.AppCompat { @@ -18,10 +19,6 @@ namespace Xamarin.Forms.Platform.Android.AppCompat ShellContentTab = shellContent; } - protected ShellFragmentContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) - { - } - public override Page Page => _page; protected override PageContainer CreatePageContainer(Context context, IVisualElementRenderer child, bool inFragment) @@ -32,7 +29,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat }; } - public override global::Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + public override AView OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { _page = ((IShellContentController)ShellContentTab).GetOrCreateContent(); return base.OnCreateView(inflater, container, savedInstanceState); diff --git a/Xamarin.Forms.Platform.Android/Extensions/FragmentManagerExtensions.cs b/Xamarin.Forms.Platform.Android/Extensions/FragmentManagerExtensions.cs index fc3b585..a2494ed 100644 --- a/Xamarin.Forms.Platform.Android/Extensions/FragmentManagerExtensions.cs +++ b/Xamarin.Forms.Platform.Android/Extensions/FragmentManagerExtensions.cs @@ -17,6 +17,11 @@ namespace Xamarin.Forms.Platform.Android return fragmentTransaction.Add(containerViewId, fragment); } + public static FragmentTransaction ReplaceEx(this FragmentTransaction fragmentTransaction, int containerViewId, Fragment fragment) + { + return fragmentTransaction.Replace(containerViewId, fragment); + } + public static FragmentTransaction HideEx(this FragmentTransaction fragmentTransaction, Fragment fragment) { return fragmentTransaction.Hide(fragment); diff --git a/Xamarin.Forms.Platform.Android/Renderers/ColorChangeRevealDrawable.cs b/Xamarin.Forms.Platform.Android/Renderers/ColorChangeRevealDrawable.cs index a2aa381..72191ad 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ColorChangeRevealDrawable.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ColorChangeRevealDrawable.cs @@ -12,6 +12,11 @@ namespace Xamarin.Forms.Platform.Android readonly AColor _endColor; readonly AColor _startColor; float _progress; + bool _disposed; + ValueAnimator _animator; + + internal AColor StartColor => _startColor; + internal AColor EndColor => _endColor; public ColorChangeRevealDrawable(AColor startColor, AColor endColor, Point center) : base() { @@ -20,11 +25,11 @@ namespace Xamarin.Forms.Platform.Android if (_startColor != _endColor) { - ValueAnimator animator = ValueAnimator.OfFloat(0, 1); - animator.SetInterpolator(new global::Android.Views.Animations.DecelerateInterpolator()); - animator.SetDuration(500); - animator.Update += OnUpdate; - animator.Start(); + _animator = ValueAnimator.OfFloat(0, 1); + _animator.SetInterpolator(new global::Android.Views.Animations.DecelerateInterpolator()); + _animator.SetDuration(500); + _animator.Update += OnUpdate; + _animator.Start(); _center = center; } else @@ -35,6 +40,9 @@ namespace Xamarin.Forms.Platform.Android public override void Draw(Canvas canvas) { + if (_disposed) + return; + if (_progress == 1) { canvas.DrawColor(_endColor); @@ -54,14 +62,39 @@ namespace Xamarin.Forms.Platform.Android { Color = _endColor }; + canvas.DrawCircle(centerX, centerY, radius * _progress, paint); } void OnUpdate(object sender, ValueAnimator.AnimatorUpdateEventArgs e) { _progress = (float)e.Animation.AnimatedValue; - if (!this.IsDisposed()) - InvalidateSelf(); + + InvalidateSelf(); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + if (_animator != null) + { + _animator.Update -= OnUpdate; + + _animator.Cancel(); + + _animator.Dispose(); + + _animator = null; + } + } + + base.Dispose(disposing); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellBottomNavViewAppearanceTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellBottomNavViewAppearanceTracker.cs index f74e20b..037272d 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellBottomNavViewAppearanceTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellBottomNavViewAppearanceTracker.cs @@ -14,7 +14,7 @@ namespace Xamarin.Forms.Platform.Android ShellItem _shellItem; ColorStateList _defaultList; bool _disposed; - Color _lastColor = Color.Default; + ColorStateList _colorStateList; public ShellBottomNavViewAppearanceTracker(IShellContext shellContext, ShellItem shellItem) { @@ -42,7 +42,6 @@ namespace Xamarin.Forms.Platform.Android var unselectedColor = controller.EffectiveTabBarUnselectedColor; var titleColor = controller.EffectiveTabBarTitleColor; - if (_defaultList == null) { #if __ANDROID_28__ @@ -52,41 +51,45 @@ namespace Xamarin.Forms.Platform.Android #endif } - var colorStateList = MakeColorStateList(titleColor, disabledColor, unselectedColor); - bottomView.ItemTextColor = colorStateList; - bottomView.ItemIconTintList = colorStateList; - - colorStateList.Dispose(); + _colorStateList = MakeColorStateList(titleColor, disabledColor, unselectedColor); + bottomView.ItemTextColor = _colorStateList; + bottomView.ItemIconTintList = _colorStateList; SetBackgroundColor(bottomView, backgroundColor); } protected virtual void SetBackgroundColor(BottomNavigationView bottomView, Color color) { - if (_lastColor.IsDefault) - _lastColor = color; - - using (var menuView = bottomView.GetChildAt(0) as BottomNavigationMenuView) + var menuView = bottomView.GetChildAt(0) as BottomNavigationMenuView; + var oldBackground = bottomView.Background; + var colorDrawable = oldBackground as ColorDrawable; + var colorChangeRevealDrawable = oldBackground as ColorChangeRevealDrawable; + AColor lastColor = colorChangeRevealDrawable?.EndColor ?? colorDrawable?.Color ?? Color.Default.ToAndroid(); + var newColor = color.ToAndroid(); + + if (menuView == null) { - if (menuView == null) + if (colorDrawable != null && lastColor == newColor) + return; + + if (lastColor != color.ToAndroid() || colorDrawable == null) { bottomView.SetBackground(new ColorDrawable(color.ToAndroid())); } - else - { - var index = _shellItem.Items.IndexOf(_shellItem.CurrentItem); - using (var menu = bottomView.Menu) - index = Math.Min(index, menu.Size() - 1); - - using (var child = menuView.GetChildAt(index)) - { - var touchPoint = new Point(child.Left + (child.Right - child.Left) / 2, child.Top + (child.Bottom - child.Top) / 2); - - bottomView.Background?.Dispose(); - bottomView.SetBackground(new ColorChangeRevealDrawable(_lastColor.ToAndroid(), color.ToAndroid(), touchPoint)); - _lastColor = color; - } - } + } + else + { + if (colorChangeRevealDrawable != null && lastColor == newColor) + return; + + var index = _shellItem.Items.IndexOf(_shellItem.CurrentItem); + var menu = bottomView.Menu; + index = Math.Min(index, menu.Size() - 1); + + var child = menuView.GetChildAt(index); + var touchPoint = new Point(child.Left + (child.Right - child.Left) / 2, child.Top + (child.Bottom - child.Top) / 2); + + bottomView.SetBackground(new ColorChangeRevealDrawable(lastColor, newColor, touchPoint)); } } @@ -135,20 +138,23 @@ namespace Xamarin.Forms.Platform.Android protected virtual void Dispose(bool disposing) { - if (!_disposed) + if (_disposed) + return; + + _disposed = true; + + if (disposing) { - if (disposing) - { - _defaultList?.Dispose(); - } + _defaultList?.Dispose(); + _colorStateList?.Dispose(); _shellItem = null; _shellContext = null; _defaultList = null; - _disposed = true; + _colorStateList = null; } } -#endregion IDisposable + #endregion IDisposable } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs index ce50e27..3925beb 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs @@ -54,6 +54,7 @@ namespace Xamarin.Forms.Platform.Android ShellContent _shellContent; Toolbar _toolbar; IShellToolbarTracker _toolbarTracker; + bool _disposed; public ShellContentFragment(IShellContext shellContext, ShellContent shellContent) { @@ -137,19 +138,9 @@ namespace Xamarin.Forms.Platform.Android return _root; } - // Use OnDestroy instead of OnDestroyView because OnDestroyView will be - // called before the animation completes. This causes tons of tiny issues. - public override void OnDestroy() + void Destroy() { - base.OnDestroy(); - - _shellPageContainer.RemoveAllViews(); - _renderer?.Dispose(); - _root?.Dispose(); - _toolbarTracker.Dispose(); - _appearanceTracker.Dispose(); - - ((IShellController)_shellContext.Shell).RemoveAppearanceObserver(this); + ((IShellController)_shellContext.Shell).RemoveAppearanceObserver(this); if (_shellContent != null) { @@ -158,11 +149,49 @@ namespace Xamarin.Forms.Platform.Android _page = null; } + if (_shellPageContainer != null) + { + _shellPageContainer.RemoveAllViews(); + + if (_root is ViewGroup vg) + vg.RemoveView(_shellPageContainer); + } + + _renderer?.Dispose(); + _root?.Dispose(); + _toolbarTracker?.Dispose(); + _appearanceTracker?.Dispose(); + + _appearanceTracker = null; - _toolbar = null; _toolbarTracker = null; + _toolbar = null; _root = null; _renderer = null; + _shellContent = null; + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + if (disposing) + { + Destroy(); + _page = null; + } + + base.Dispose(disposing); + } + + // Use OnDestroy instead of OnDestroyView because OnDestroyView will be + // called before the animation completes. This causes tons of tiny issues. + public override void OnDestroy() + { + base.OnDestroy(); + Destroy(); } protected virtual void ResetAppearance() => _appearanceTracker.ResetAppearance(_toolbar, _toolbarTracker); diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs index 3d63a5c..e1fa1f5 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRecyclerAdapter.cs @@ -22,7 +22,12 @@ namespace Xamarin.Forms.Platform.Android List _listItems; Dictionary _templateMap = new Dictionary(); - readonly Action _selectedCallback; + + Action _selectedCallback; + + bool _disposed; + + ElementViewHolder _elementViewHolder; public ShellFlyoutRecyclerAdapter(IShellContext shellContext, Action selectedCallback) { @@ -55,7 +60,7 @@ namespace Xamarin.Forms.Platform.Android else { dataTemplate = Shell.GetItemTemplate(item.Element) ?? Shell.ItemTemplate ?? DefaultItemTemplate; - } + } var template = dataTemplate.SelectDataTemplate(item.Element, Shell); var id = ((IDataTemplateController)template).Id; @@ -82,7 +87,7 @@ namespace Xamarin.Forms.Platform.Android AView ITabStop.TabStop => this; -#region IVisualElementRenderer + #region IVisualElementRenderer VisualElement IVisualElementRenderer.Element => Content?.BindingContext as VisualElement; @@ -105,7 +110,7 @@ namespace Xamarin.Forms.Platform.Android public event EventHandler ElementPropertyChanged; #pragma warning restore 67 -#endregion IVisualElementRenderer + #endregion IVisualElementRenderer internal View Content { get; set; } @@ -163,7 +168,9 @@ namespace Xamarin.Forms.Platform.Android container.LayoutParameters = new LP(LP.MatchParent, LP.WrapContent); linearLayout.AddView(container); - return new ElementViewHolder(content, linearLayout, bar, _selectedCallback); + _elementViewHolder = new ElementViewHolder(content, linearLayout, bar, _selectedCallback); + + return _elementViewHolder; } protected virtual List GenerateItemList() @@ -198,7 +205,7 @@ namespace Xamarin.Forms.Platform.Android { var grid = new Grid(); var groups = new VisualStateGroupList(); - + var commonGroup = new VisualStateGroup(); commonGroup.Name = "CommonStates"; groups.Add(commonGroup); @@ -242,6 +249,27 @@ namespace Xamarin.Forms.Platform.Android return grid; } + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + ((IShellController)Shell).StructureChanged -= OnShellStructureChanged; + + _elementViewHolder?.Dispose(); + + _listItems = null; + _selectedCallback = null; + _elementViewHolder = null; + } + + base.Dispose(disposing); + } + public class AdapterListItem { public AdapterListItem(Element element, bool drawTopLine = false) @@ -256,9 +284,10 @@ namespace Xamarin.Forms.Platform.Android public class ElementViewHolder : RecyclerView.ViewHolder { - readonly Action _selectedCallback; + Action _selectedCallback; Element _element; AView _itemView; + bool _disposed; public ElementViewHolder(View view, AView itemView, AView bar, Action selectedCallback) : base(itemView) { @@ -321,6 +350,25 @@ namespace Xamarin.Forms.Platform.Android _selectedCallback(Element); } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + _itemView.Click -= OnClicked; + + Element = null; + _itemView = null; + _selectedCallback = null; + } + + base.Dispose(disposing); + } } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRenderer.cs index a77cd56..b8e15a8 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutRenderer.cs @@ -59,6 +59,7 @@ namespace Xamarin.Forms.Platform.Android IShellFlyoutContentRenderer _flyoutContent; int _flyoutWidth; int _currentLockMode; + bool _disposed; public ShellFlyoutRenderer(IShellContext shellContext, Context context) : base(context) { @@ -170,5 +171,28 @@ namespace Xamarin.Forms.Platform.Android SetScrimColor(behavior == FlyoutBehavior.Locked ? Color.Transparent.ToAndroid() : (int)DefaultScrimColor); } } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + Shell.PropertyChanged -= OnShellPropertyChanged; + + RemoveDrawerListener(this); + ((IShellController)_shellContext.Shell).RemoveFlyoutBehaviorObserver(this); + + RemoveView(_content); + RemoveView(_flyoutContent.AndroidView); + + _flyoutContent.Dispose(); + } + + base.Dispose(disposing); + } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs index 8f72d8b..f162c60 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFlyoutTemplatedContentRenderer.cs @@ -1,6 +1,5 @@ using Android.Content; using Android.Graphics.Drawables; -using Android.Runtime; using Android.Support.Design.Widget; using Android.Support.V7.Widget; using Android.Util; @@ -23,18 +22,21 @@ namespace Xamarin.Forms.Platform.Android #endregion IShellFlyoutContentRenderer - IShellContext _shellContext; - bool _disposed; - HeaderContainer _headerView; - ViewGroup _rootView; - Drawable _defaultBackgroundColor; + IShellContext _shellContext; + bool _disposed; + HeaderContainer _headerView; + ViewGroup _rootView; + Drawable _defaultBackgroundColor; ImageView _bgImage; - View _flyoutHeader; - int _actionBarHeight; + AppBarLayout _appBar; + RecyclerView _recycler; + ShellFlyoutRecyclerAdapter _adapter; + View _flyoutHeader; + int _actionBarHeight; - public ShellFlyoutTemplatedContentRenderer(IShellContext shellContext) - { - _shellContext = shellContext; + public ShellFlyoutTemplatedContentRenderer(IShellContext shellContext) + { + _shellContext = shellContext; LoadView(shellContext); } @@ -51,17 +53,19 @@ namespace Xamarin.Forms.Platform.Android } var coordinator = LayoutInflater.FromContext(context).Inflate(Resource.Layout.FlyoutContent, null); - var recycler = coordinator.FindViewById(Resource.Id.flyoutcontent_recycler); - var appBar = coordinator.FindViewById(Resource.Id.flyoutcontent_appbar); + + _recycler = coordinator.FindViewById(Resource.Id.flyoutcontent_recycler); + + _appBar = coordinator.FindViewById(Resource.Id.flyoutcontent_appbar); _rootView = coordinator as ViewGroup; - appBar.AddOnOffsetChangedListener(this); + _appBar.AddOnOffsetChangedListener(this); _actionBarHeight = (int)context.ToPixels(56); _flyoutHeader = ((IShellController)shellContext.Shell).FlyoutHeader; - if(_flyoutHeader != null) + if (_flyoutHeader != null) _flyoutHeader.MeasureInvalidated += OnFlyoutHeaderMeasureInvalidated; _headerView = new HeaderContainer(context, _flyoutHeader) @@ -73,13 +77,12 @@ namespace Xamarin.Forms.Platform.Android { ScrollFlags = AppBarLayout.LayoutParams.ScrollFlagScroll }; - appBar.AddView(_headerView); + _appBar.AddView(_headerView); - var adapter = new ShellFlyoutRecyclerAdapter(shellContext, OnElementSelected); - recycler.SetPadding(0, (int)context.ToPixels(20), 0, 0); - recycler.SetClipToPadding(false); - recycler.SetLayoutManager(new LinearLayoutManager(context, (int)Orientation.Vertical, false)); - recycler.SetAdapter(adapter); + _adapter = new ShellFlyoutRecyclerAdapter(shellContext, OnElementSelected); + _recycler.SetClipToPadding(false); + _recycler.SetLayoutManager(new LinearLayoutManager(context, (int)Orientation.Vertical, false)); + _recycler.SetAdapter(_adapter); var metrics = context.Resources.DisplayMetrics; var width = Math.Min(metrics.WidthPixels, metrics.HeightPixels); @@ -102,14 +105,14 @@ namespace Xamarin.Forms.Platform.Android }; UpdateFlyoutHeaderBehavior(); - _shellContext.Shell.PropertyChanged += OnShellPropertyChanged; + _shellContext.Shell.PropertyChanged += OnShellPropertyChanged; - UpdateFlyoutBackground(); - } + UpdateFlyoutBackground(); + } void OnFlyoutHeaderMeasureInvalidated(object sender, EventArgs e) { - if(_headerView != null) + if (_headerView != null) UpdateFlyoutHeaderBehavior(); } @@ -118,8 +121,8 @@ namespace Xamarin.Forms.Platform.Android ((IShellController)_shellContext.Shell).OnFlyoutItemSelected(element); } - protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEventArgs e) - { + protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEventArgs e) + { if (e.PropertyName == Shell.FlyoutHeaderBehaviorProperty.PropertyName) UpdateFlyoutHeaderBehavior(); else if (e.IsOneOf( @@ -127,7 +130,7 @@ namespace Xamarin.Forms.Platform.Android Shell.FlyoutBackgroundImageProperty, Shell.FlyoutBackgroundImageAspectProperty)) UpdateFlyoutBackground(); - } + } protected virtual void UpdateFlyoutBackground() { @@ -179,7 +182,7 @@ namespace Xamarin.Forms.Platform.Android if (_rootView.IndexOfChild(_bgImage) == -1) { - if(_bgImage.SetElevation(float.MinValue)) + if (_bgImage.SetElevation(float.MinValue)) _rootView.AddView(_bgImage); else _rootView.AddView(_bgImage, 0); @@ -191,10 +194,7 @@ namespace Xamarin.Forms.Platform.Android { var context = _shellContext.AndroidContext; - Thickness margin = default(Thickness); - - if (_flyoutHeader != null) - margin = _flyoutHeader.Margin; + var margin = _flyoutHeader?.Margin ?? default(Thickness); var minimumHeight = Convert.ToInt32(_actionBarHeight + context.ToPixels(margin.Top) - context.ToPixels(margin.Bottom)); _headerView.SetMinimumHeight(minimumHeight); @@ -245,31 +245,49 @@ namespace Xamarin.Forms.Platform.Android _headerView.SetPadding(0, -verticalOffset, 0, 0); } - protected override void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - _shellContext.Shell.PropertyChanged -= OnShellPropertyChanged; - - if (_flyoutHeader != null) - _flyoutHeader.MeasureInvalidated += OnFlyoutHeaderMeasureInvalidated; - - _headerView.Dispose(); - _rootView.Dispose(); - _defaultBackgroundColor?.Dispose(); - _bgImage?.Dispose(); + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + _shellContext.Shell.PropertyChanged -= OnShellPropertyChanged; + + if (_flyoutHeader != null) + _flyoutHeader.MeasureInvalidated -= OnFlyoutHeaderMeasureInvalidated; + + if (_appBar != null) + { + _appBar.RemoveOnOffsetChangedListener(this); + _appBar.RemoveView(_headerView); } - _flyoutHeader = null; - _defaultBackgroundColor = null; - _bgImage = null; + if (_recycler != null) + { + _recycler.SetLayoutManager(null); + _recycler.SetAdapter(null); + _recycler.Dispose(); + } + + _adapter?.Dispose(); + _headerView.Dispose(); + _rootView.Dispose(); + _defaultBackgroundColor?.Dispose(); + _bgImage?.Dispose(); + + _flyoutHeader = null; _rootView = null; - _headerView = null; - _shellContext = null; - _disposed = true; - } + _headerView = null; + _shellContext = null; + _appBar = null; + _recycler = null; + _adapter = null; + _defaultBackgroundColor = null; + _bgImage = null; + } base.Dispose(disposing); } @@ -281,18 +299,6 @@ namespace Xamarin.Forms.Platform.Android { } - public HeaderContainer(Context context, IAttributeSet attribs) : base(context, attribs) - { - } - - public HeaderContainer(Context context, IAttributeSet attribs, int defStyleAttr) : base(context, attribs, defStyleAttr) - { - } - - protected HeaderContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) - { - } - protected override void LayoutView(double x, double y, double width, double height) { var context = Context; @@ -308,4 +314,4 @@ namespace Xamarin.Forms.Platform.Android } } } -} \ No newline at end of file +} diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellFragmentPagerAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellFragmentPagerAdapter.cs index 5c467d6..58cb805 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellFragmentPagerAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellFragmentPagerAdapter.cs @@ -64,14 +64,19 @@ namespace Xamarin.Forms.Platform.Android protected override void Dispose(bool disposing) { - base.Dispose(disposing); - if (disposing && !_disposed) + if (_disposed) + return; + + _disposed = true; + + if (disposing) { ((INotifyCollectionChanged)_shellSection.Items).CollectionChanged -= OnItemsCollectionChanged; - _shellSection = null; - _disposed = true; + _shellSection = null; } + + base.Dispose(disposing); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs index d5d1c64..22bb688 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRenderer.cs @@ -48,6 +48,8 @@ namespace Xamarin.Forms.Platform.Android FrameLayout _navigationArea; AView _outerLayout; IShellBottomNavViewAppearanceTracker _appearanceTracker; + BottomSheetDialog _bottomSheetDialog; + bool _disposed; public ShellItemRenderer(IShellContext shellContext) : base(shellContext) { @@ -76,29 +78,55 @@ namespace Xamarin.Forms.Platform.Android return _outerLayout; } - // Use OnDestory become OnDestroyView may fire before events are completed. - public override void OnDestroy() + + void Destroy() { - UnhookEvents(ShellItem); + if(ShellItem != null) + UnhookEvents(ShellItem); + + ((IShellController)ShellContext.Shell).RemoveAppearanceObserver(this); + + if (_bottomSheetDialog != null) + { + _bottomSheetDialog.DismissEvent -= OnMoreSheetDismissed; + _bottomSheetDialog?.Dispose(); + _bottomSheetDialog = null; + } + + _navigationArea?.Dispose(); + _appearanceTracker?.Dispose(); + _outerLayout?.Dispose(); + if (_bottomView != null) { _bottomView?.SetOnNavigationItemSelectedListener(null); _bottomView?.Background?.Dispose(); _bottomView?.Dispose(); - _bottomView = null; + } - _navigationArea?.Dispose(); - _navigationArea = null; + _bottomView = null; + _navigationArea = null; + _appearanceTracker = null; + _outerLayout = null; - _appearanceTracker?.Dispose(); - _appearanceTracker = null; + } - _outerLayout?.Dispose(); - _outerLayout = null; - } + protected override void Dispose(bool disposing) + { + if (_disposed) + return; - ((IShellController)ShellContext.Shell).RemoveAppearanceObserver(this); + _disposed = true; + if (disposing) + Destroy(); + base.Dispose(disposing); + } + + // Use OnDestory become OnDestroyView may fire before events are completed. + public override void OnDestroy() + { + Destroy(); base.OnDestroy(); } @@ -228,9 +256,9 @@ namespace Xamarin.Forms.Platform.Android var id = item.ItemId; if (id == MoreTabId) { - var bottomSheetDialog = CreateMoreBottomSheet(OnMoreItemSelected); - bottomSheetDialog.Show(); - bottomSheetDialog.DismissEvent += OnMoreSheetDismissed; + _bottomSheetDialog = CreateMoreBottomSheet(OnMoreItemSelected); + _bottomSheetDialog.Show(); + _bottomSheetDialog.DismissEvent += OnMoreSheetDismissed; } else { @@ -256,7 +284,17 @@ namespace Xamarin.Forms.Platform.Android dialog.Dispose(); } - protected virtual void OnMoreSheetDismissed(object sender, EventArgs e) => OnShellSectionChanged(); + protected virtual void OnMoreSheetDismissed(object sender, EventArgs e) + { + OnShellSectionChanged(); + + if (_bottomSheetDialog != null) + { + _bottomSheetDialog.DismissEvent -= OnMoreSheetDismissed; + _bottomSheetDialog.Dispose(); + _bottomSheetDialog = null; + } + } protected override void OnShellItemsChanged(object sender, NotifyCollectionChangedEventArgs e) { diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRendererBase.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRendererBase.cs index e3fbc3b..348ff2c 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellItemRendererBase.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellItemRendererBase.cs @@ -31,6 +31,7 @@ namespace Xamarin.Forms.Platform.Android IShellObservableFragment _currentFragment; ShellSection _shellSection; Page _displayedPage; + bool _disposed; protected ShellItemRendererBase(IShellContext shellContext) { @@ -82,12 +83,14 @@ namespace Xamarin.Forms.Platform.Android return ShellContext.CreateFragmentForPage(page); } - public override void OnDestroy() + void Destroy() { - base.OnDestroy(); - foreach (var item in _fragmentMap) + { + RemoveFragment(item.Value.Fragment); item.Value.Fragment.Dispose(); + } + _fragmentMap.Clear(); ShellSection = null; @@ -96,6 +99,25 @@ namespace Xamarin.Forms.Platform.Android Destroyed?.Invoke(this, EventArgs.Empty); } + public override void OnDestroy() + { + base.OnDestroy(); + Destroy(); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + Destroy(); + + base.Dispose(disposing); + } + protected abstract ViewGroup GetNavigationTarget(); protected virtual IShellObservableFragment GetOrCreateFragmentForTab(ShellSection shellSection) @@ -158,7 +180,7 @@ namespace Xamarin.Forms.Platform.Android // We need to handle this after we know what the target is // because we might accidentally remove an already added target. // Then there would be two transactions in a row, one removing and one adding - // the same fragement and things get really screwy when you do that. + // the same fragment and things get really screwy when you do that. break; default: @@ -200,11 +222,11 @@ namespace Xamarin.Forms.Platform.Android trackFragment = target; if (_currentFragment != null) - t.Hide(_currentFragment.Fragment); + t.HideEx(_currentFragment.Fragment); if (!target.Fragment.IsAdded) - t.Add(GetNavigationTarget().Id, target.Fragment); - t.Show(target.Fragment); + t.AddEx(GetNavigationTarget().Id, target.Fragment); + t.ShowEx(target.Fragment); break; case ShellNavigationSource.Pop: @@ -213,10 +235,10 @@ namespace Xamarin.Forms.Platform.Android trackFragment = _currentFragment; if (_currentFragment != null) - t.Remove(_currentFragment.Fragment); + t.RemoveEx(_currentFragment.Fragment); if (!target.Fragment.IsAdded) - t.Add(GetNavigationTarget().Id, target.Fragment); + t.AddEx(GetNavigationTarget().Id, target.Fragment); t.Show(target.Fragment); break; } @@ -237,7 +259,7 @@ namespace Xamarin.Forms.Platform.Android result.TrySetResult(true); } - t.CommitAllowingStateLoss(); + t.CommitAllowingStateLossEx(); _currentFragment = target; @@ -346,7 +368,7 @@ namespace Xamarin.Forms.Platform.Android void RemoveAllButCurrent(Fragment skip) { - var trans = ChildFragmentManager.BeginTransaction(); + var trans = ChildFragmentManager.BeginTransactionEx(); foreach (var kvp in _fragmentMap) { var f = kvp.Value.Fragment; @@ -354,7 +376,7 @@ namespace Xamarin.Forms.Platform.Android continue; trans.Remove(f); }; - trans.CommitAllowingStateLoss(); + trans.CommitAllowingStateLossEx(); } void RemoveAllPushedPages(ShellSection shellSection, bool keepCurrent) @@ -362,7 +384,7 @@ namespace Xamarin.Forms.Platform.Android if (shellSection.Stack.Count <= 1 || (keepCurrent && shellSection.Stack.Count == 2)) return; - var t = ChildFragmentManager.BeginTransaction(); + var t = ChildFragmentManager.BeginTransactionEx(); foreach (var kvp in _fragmentMap.ToList()) { @@ -374,17 +396,17 @@ namespace Xamarin.Forms.Platform.Android if (keepCurrent && kvp.Value.Fragment == _currentFragment) continue; - t.Remove(kvp.Value.Fragment); + t.RemoveEx(kvp.Value.Fragment); } - t.CommitAllowingStateLoss(); + t.CommitAllowingStateLossEx(); } void RemoveFragment(Fragment fragment) { - var t = ChildFragmentManager.BeginTransaction(); - t.Remove(fragment); - t.CommitAllowingStateLoss(); + var t = ChildFragmentManager.BeginTransactionEx(); + t.RemoveEx(fragment); + t.CommitAllowingStateLossEx(); } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs index 1b4b0c6..6fc424e 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellRenderer.cs @@ -246,10 +246,10 @@ namespace Xamarin.Forms.Platform.Android FragmentTransaction transaction = manager.BeginTransaction(); if (animate) - transaction.SetTransition((int)global::Android.App.FragmentTransit.EnterMask); + transaction.SetTransitionEx((int)global::Android.App.FragmentTransit.EnterMask); - transaction.Replace(_frameLayout.Id, fragment); - transaction.CommitAllowingStateLoss(); + transaction.ReplaceEx(_frameLayout.Id, fragment); + transaction.CommitAllowingStateLossEx(); void OnDestroyed (object sender, EventArgs args) { @@ -332,15 +332,17 @@ namespace Xamarin.Forms.Platform.Android { var bounds = Bounds; - var paint = new Paint(); + using (var paint = new Paint()) + { - paint.Color = _color; + paint.Color = _color; - canvas.DrawRect(new Rect(0, 0, bounds.Right, _topSize), paint); + canvas.DrawRect(new Rect(0, 0, bounds.Right, _topSize), paint); - canvas.DrawRect(new Rect(0, bounds.Bottom - _bottomSize, bounds.Right, bounds.Bottom), paint); + canvas.DrawRect(new Rect(0, bounds.Bottom - _bottomSize, bounds.Right, bounds.Bottom), paint); - paint.Dispose(); + paint.Dispose(); + } } public override void SetAlpha(int alpha) @@ -361,20 +363,37 @@ namespace Xamarin.Forms.Platform.Android protected virtual void Dispose(bool disposing) { - if (!_disposed) + if (_disposed) + return; + + _disposed = true; + + if (disposing) { - if (disposing) + if (_currentRenderer != null && _currentRenderer.Fragment.IsAlive()) { - Element.PropertyChanged -= OnElementPropertyChanged; - Element.SizeChanged -= OnElementSizeChanged; + FragmentTransaction transaction = FragmentManager.BeginTransaction(); + transaction.RemoveEx(_currentRenderer.Fragment); + transaction.CommitAllowingStateLossEx(); + FragmentManager.ExecutePendingTransactionsEx(); } - Element = null; - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. + Element.PropertyChanged -= OnElementPropertyChanged; + Element.SizeChanged -= OnElementSizeChanged; + ((IShellController)Element).RemoveAppearanceObserver(this); - _disposed = true; + // This cast is necessary because IShellFlyoutRenderer doesn't implement IDisposable + (_flyoutRenderer as IDisposable)?.Dispose(); + + _currentRenderer.Dispose(); + _currentRenderer = null; } + + Element = null; + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + _disposed = true; } #endregion IDisposable diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs index d861819..fadd72f 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchView.cs @@ -123,20 +123,18 @@ namespace Xamarin.Forms.Platform.Android protected override void Dispose(bool disposing) { - base.Dispose(disposing); + if (_disposed) + return; if (disposing) { _disposed = true; - _searchHandlerAppearanceTracker?.Dispose(); SearchHandler.PropertyChanged -= OnSearchHandlerPropertyChanged; _textBlock.ItemClick -= OnTextBlockItemClicked; _textBlock.RemoveTextChangedListener(this); _textBlock.SetOnEditorActionListener(null); - _textBlock.Adapter.Dispose(); - _textBlock.Adapter = null; _textBlock.DropDownBackground.Dispose(); _textBlock.SetDropDownBackgroundDrawable(null); @@ -144,6 +142,9 @@ namespace Xamarin.Forms.Platform.Android _clearPlaceholderButton.Click -= OnClearPlaceholderButtonClicked; _searchButton.Click -= OnSearchButtonClicked; + _textBlock.Adapter.Dispose(); + _textBlock.Adapter = null; + _searchHandlerAppearanceTracker?.Dispose(); _textBlock.Dispose(); _clearButton.Dispose(); _searchButton.Dispose(); @@ -160,6 +161,8 @@ namespace Xamarin.Forms.Platform.Android _searchHandlerAppearanceTracker = null; SearchHandler = null; + + base.Dispose(disposing); } protected virtual void LoadView(SearchHandler searchHandler) diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs index 6e0e07d..2e6b66a 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellSearchViewAdapter.cs @@ -19,6 +19,7 @@ namespace Xamarin.Forms.Platform.Android Filter _filter; IReadOnlyList _emptyList = new List(); IReadOnlyList ListProxy => SearchController.ListProxy ?? _emptyList; + bool _disposed; public ShellSearchViewAdapter(SearchHandler searchHandler, IShellContext shellContext) { @@ -30,19 +31,24 @@ namespace Xamarin.Forms.Platform.Android protected override void Dispose(bool disposing) { - base.Dispose(disposing); + if (_disposed) + return; + + _disposed = true; if (disposing) { SearchController.ListProxyChanged -= OnListPropxyChanged; _searchHandler.PropertyChanged -= OnSearchHandlerPropertyChanged; - _filter.Dispose(); + _filter?.Dispose(); } _filter = null; _shellContext = null; _searchHandler = null; _defaultTemplate = null; + + base.Dispose(disposing); } public Filter Filter => _filter ?? (_filter = new CustomFilter(this)); diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellSectionRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellSectionRenderer.cs index 2c1b331..3f4fc60 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellSectionRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellSectionRenderer.cs @@ -113,16 +113,13 @@ namespace Xamarin.Forms.Platform.Android IShellToolbarAppearanceTracker _toolbarAppearanceTracker; IShellToolbarTracker _toolbarTracker; FormsViewPager _viewPager; + bool _disposed; public ShellSectionRenderer(IShellContext shellContext) { _shellContext = shellContext; } - protected ShellSectionRenderer(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) - { - } - public event EventHandler AnimationFinished; Fragment IShellObservableFragment.Fragment => this; @@ -171,19 +168,17 @@ namespace Xamarin.Forms.Platform.Android return _rootView = root; } - // Use OnDestroy instead of OnDestroyView because OnDestroyView will be - // called before the animation completes. This causes tons of tiny issues. - public override void OnDestroy() + void Destroy() { if (_rootView != null) { UnhookEvents(); + _viewPager.RemoveOnPageChangeListener(this); var adapter = _viewPager.Adapter; _viewPager.Adapter = null; adapter.Dispose(); - _viewPager.RemoveOnPageChangeListener(this); _toolbarAppearanceTracker.Dispose(); _tabLayoutAppearanceTracker.Dispose(); @@ -202,8 +197,27 @@ namespace Xamarin.Forms.Platform.Android _viewPager = null; _rootView = null; + } + + // Use OnDestroy instead of OnDestroyView because OnDestroyView will be + // called before the animation completes. This causes tons of tiny issues. + public override void OnDestroy() + { + Destroy(); base.OnDestroy(); } + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + Destroy(); + } + } protected virtual void OnAnimationFinished(EventArgs e) { diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellTabLayoutAppearanceTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellTabLayoutAppearanceTracker.cs index 4389387..003cd50 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellTabLayoutAppearanceTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellTabLayoutAppearanceTracker.cs @@ -37,8 +37,7 @@ namespace Xamarin.Forms.Platform.Android var unselectedArgb = unselected.ToAndroid(ShellRenderer.DefaultUnselectedColor).ToArgb(); tabLayout.SetTabTextColors(unselectedArgb, titleArgb); - using (var colorDrawable = new ColorDrawable(background.ToAndroid(ShellRenderer.DefaultBackgroundColor))) - tabLayout.SetBackground(colorDrawable); + tabLayout.SetBackground(new ColorDrawable(background.ToAndroid(ShellRenderer.DefaultBackgroundColor))); tabLayout.SetSelectedTabIndicatorColor(foreground.ToAndroid(ShellRenderer.DefaultForegroundColor)); } @@ -51,15 +50,11 @@ namespace Xamarin.Forms.Platform.Android protected virtual void Dispose(bool disposing) { - if (!_disposed) - { - if (disposing) - { - } + if (_disposed) + return; - _shellContext = null; - _disposed = true; - } + _disposed = true; + _shellContext = null; } #endregion IDisposable diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarAppearanceTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarAppearanceTracker.cs index ebc8bc0..f7f686e 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarAppearanceTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarAppearanceTracker.cs @@ -47,13 +47,14 @@ namespace Xamarin.Forms.Platform.Android protected virtual void Dispose(bool disposing) { - if (!_disposed) + if (_disposed) + return; + + _disposed = true; + + if (disposing) { - if (disposing) - { - } _shellContext = null; - _disposed = true; } } diff --git a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs index f2fb8ed..460e849 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ShellToolbarTracker.cs @@ -128,8 +128,6 @@ namespace Xamarin.Forms.Platform.Android else _shellContext.Shell.FlyoutIsPresented = !_shellContext.Shell.FlyoutIsPresented; } - - v.Dispose(); } protected override void Dispose(bool disposing) @@ -137,11 +135,16 @@ namespace Xamarin.Forms.Platform.Android if (_disposed) return; + _disposed = true; + if (disposing) { + if (_backButtonBehavior != null) + _backButtonBehavior.PropertyChanged -= OnBackButtonBehaviorChanged; + ((IShellController)_shellContext.Shell).RemoveFlyoutBehaviorObserver(this); + UpdateTitleView(_shellContext.AndroidContext, _toolbar, null); - _drawerToggle?.Dispose(); if (_searchView != null) { _searchView.View.RemoveFromParent(); @@ -150,10 +153,7 @@ namespace Xamarin.Forms.Platform.Android _searchView.Dispose(); } - ((IShellController)_shellContext.Shell).RemoveFlyoutBehaviorObserver(this); - - if (_backButtonBehavior != null) - _backButtonBehavior.PropertyChanged -= OnBackButtonBehaviorChanged; + _drawerToggle?.Dispose(); } _backButtonBehavior = null; @@ -164,7 +164,6 @@ namespace Xamarin.Forms.Platform.Android Page = null; _toolbar = null; _drawerLayout = null; - _disposed = true; base.Dispose(disposing); } -- 2.7.4