Forum Xamarin.Forms

Cannot access a disposed object -> AppCompat.NavigationPageRenderer

MatthewSoucoupMatthewSoucoup USMember, Insider, University, Developer Group Leader mod

I'm getting a strange exception on when trying to change the text of a ToolbarItem within a ContentPage on Android. This happens when the content page is hosted within a navigation page, and the navigation page itself is within a tabbed page.

When the user navigates between the tabs, then comes back & performs an action that results in the toolbar item's text being changed the following exception occurs:

Cannot access a disposed object.
Object name: 'Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer'.

Full stack trace attached.

App to reproduce is here: https://github.com/codemillmatt/droidcrash

Steps:
1. Navigate between tabs
2. Click on one of the toolbar buttons on first tab

Any thoughts?

Answers

  • MarcoMinervaMarcoMinerva ITMember

    I have the same issue, on my own project, after update to Xamarin v4.1.1.3.

  • PhilippeFauconPhilippeFaucon USMember

    I'm also experiencing the same problem, although for me it is an icon that is changing.

  • JGoldbergerJGoldberger USMember, Forum Administrator, Xamarin Team, University Xamurai

    Hi,

    I went ahead and filed a bug report using the github repro project linked in the first post on this thread. Link to bug report:
    https://bugzilla.xamarin.com/show_bug.cgi?id=42678

  • I've got the same problem, does anyone have a workaround for this?

  • kamukamu JPMember ✭✭

    I also encountered the same problem.
    This problem does not occur if up to two tabs, occurred in the case of the tab is three or more.

  • kamukamu JPMember ✭✭

    I was able to avoid this problem in the following code.

    public class FixNavigationPageRenderer:NavigationPageRenderer
    {
        protected override void Dispose(bool disposing) {
    
            var fieldInfo = typeof(NavigationPageRenderer).GetField("_toolbarTracker", BindingFlags.Instance | BindingFlags.NonPublic);
            var toolbarTracker = (ToolbarTracker)fieldInfo.GetValue(this);
    
            var methodInfo = typeof(NavigationPageRenderer).GetMethod("HandleToolbarItemPropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
    
            Action<ToolbarItem> removeEvent = (item) => {
                Delegate eventMethod = methodInfo.CreateDelegate(typeof(PropertyChangedEventHandler),this);
                item.GetType().GetEvent("PropertyChanged").RemoveEventHandler(item, eventMethod);
            };
    
            foreach (var item in toolbarTracker.ToolbarItems) {
                removeEvent(item);
            }
    
            base.Dispose(disposing);
        }
    }
    

    I think that the cause of this probrem is to not rerealsed ToolbarItem EventHandler.

  • CKremppCKrempp USMember

    I too am experiencing the problem, but for the Xamarin.Forms.Platform.Android.AppCompat.FrameRenderer, when attempting to just modify the children of a grid. I agree with @kamu, there is something about having 3 or more tabs that causes the issue. My theory is that it has something to do with the way the TabbedPage performs its disposal/clean-up of pages that are not in the visible range.

    From what I have observed the visible range is the current page and the page to the left of the current, the page to the right appears to just be an index or pointer to identify what the next page is. This explains why having 2 tabs does not cause the issue, but three does.

  • madgrizzlemadgrizzle USMember

    I'm using the toolbar command as the means for users to login/logout of a server. My main view has three tabs and I have to change the name in the toolbar (Login or Logout) on each of those three tabs after a user does a login or logout. At the moment, I've made it so users can only log in from one specific tab to avoid this crash from happening... but it seems really awkward when you use my app to have to go to a specific tab to perform login/logout.

    I tried to use the OnAppearing method to control it but it fired OnAppearing on Tab 1 (also) when I switched from Tab 3 to Tab 2... and it crashed.

  • kamukamu JPMember ✭✭

    Another solution by Effects

    [assembly: ResolutionGroupName ("Xamarin")]
    [assembly: ExportEffect(typeof(TabbedPageLimit), "TabbedPageLimit")]
    namespace MyProject.Droid.Effects
    {
        public class TabbedPageLimit:PlatformEffect
        {
    
            protected override void OnAttached() {
    
                var fieldInfo = typeof(TabbedPageRenderer).GetField("_viewPager", BindingFlags.Instance | BindingFlags.NonPublic);
                var viewPager = (ViewPager)fieldInfo.GetValue(Container);
    
                viewPager.OffscreenPageLimit = 2; //max tab count - 1
            }
    
            protected override void OnDetached() {
    
            }
        }
    }
    

    how to use.

    var tab = new TabbedPage();
    tab.Effects.Add( Effect.Resolve( "Xamarin.TabbedPageLimit" ) );
    

    Dispose will not occur by setting OffscreenPageLimit.

  • GiorgosPapadakisGiorgosPapadakis USMember ✭✭
    edited September 2016

    The same error happen when i change the toobaritem Icon but i do not have any tabbed control in content page.
    My app is a master detail where master is my menu and detail my navigation pages. When i open the first navigation page everything works. It crashes when i navigate to another page and then come back and perform the change icon.

    ToolbarItems[0].Icon = Device.OnPlatform("ic_list_white", "ic_list_white_24dp.png", "");

  • KentCHKentCH USUniversity

    @kamu's first solution worked for me, although I had to adapt it a little because ToolbarTracker is not a publicly available type:

    public sealed class WorkaroundNavigationPageRenderer : NavigationPageRenderer
    {
        protected override void Dispose(bool disposing)
        {
            var toolbarTracker = typeof(NavigationPageRenderer)
                .GetField("_toolbarTracker", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(this);
            var toolbarItems = (IEnumerable<ToolbarItem>)toolbarTracker
                .GetType()
                .GetProperty("ToolbarItems", BindingFlags.Instance | BindingFlags.Public)
                .GetValue(toolbarTracker);
            var methodInfo = typeof(NavigationPageRenderer)
                .GetMethod("HandleToolbarItemPropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
    
            Action<ToolbarItem> removeEvent =
                (item) =>
                {
                    var eventMethod = methodInfo.CreateDelegate(typeof(PropertyChangedEventHandler), this);
                    item
                        .GetType()
                        .GetEvent("PropertyChanged")
                        .RemoveEventHandler(item, eventMethod);
                };
    
            foreach (var item in toolbarItems)
            {
                removeEvent(item);
            }
    
            base.Dispose(disposing);
        }
    }
    
  • Ryan_SouthRyan_South USMember

    Thanks for the code snippets also had the same problem.

  • Vinicius_PozzaneVinicius_Pozzane USUniversity

    The object cannot disposed completely then manke a renderer, like this:

    public class CustomLabelRenderer : LabelRenderer
    {
    protected override void Dispose(bool disposing)
    {
    base.Dispose(disposing);
    GC.Collect();
    }
    }

    For me works!!!

  • ThomasAntlingerThomasAntlinger ATMember ✭✭

    I had same Problem when i changed or replaced Visual Components (Xamarin.Forms). They still tried to Layout them, and also had Problem with Binding. They still tried to update them, even the Native Component was already gone.
    I could Solve the Problem by setting them Invisible, because the Xamarin.Forms Code at least respects this Property. And if i close a Page i also had to make the components invisible, because even closing a page could crash the app because of this error.

    Took me long time to check out these "random" errors, and the have been dificult to follow because they behaved different in Debug/Release and Device because they depend on timing. And occur due multithreading.

    public static void SetAllInvisible(this Element element) {
    if (element is ILayoutController) {
    ILayoutController layout = element as ILayoutController;
    foreach (Element subelement in layout.Children) {
    SetAllInvisible(subelement);
    }
    }
    if (element is View) {
    View view = element as View;
    view.IsVisible = false;
    }
    }

  • midixmidix LVMember ✭✭

    I have the same System.ObjectDisposedException: Cannot access a disposed object. issue during fast navigation forth/back between views. From stack trace, it seems the culprit is somewhere here:
    https://github.com/xamarin/Xamarin.Forms/blob/release-2.3.2/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs#L469

    and then it goes deeper in generated classes: Mono.Android/platforms/android-25/src/generated/Android.Views.View.cs ...

    My guess is that on some occasions the native view is no longer valid (it is disposed, as exception says) but Xamarin does not know that and still tries to extract Context out of the view.

    All the snippets in this thread did not help. I guess, there should be some safer way how to check inside the page renderer if the current native View has gone and we should not attempt to access Context, but I'm not sure how to deal with that situation gracefully - just bail off? Won't that break the functionality? If the exception occurs when we navigate away, then it's no big deal, the next view will be constructed and call OnAttachedToWindow again. But I have doubts.

  • midixmidix LVMember ✭✭

    I was looking at these two code fragments in Xamarin source:

            void UpdateToolbar()
        {
            if (_disposed)
                return;
            Context context = Context;
    

    and

        void RegisterToolbar()
            {
                Context context = Context;
    

    Clearly they have forgotten to check for _disposed in the RegisterToolbar.

    Unfortunately, RegisterToolbar cannot be overriden and _disposed is private, so I tried override Dispose method and duplicate the variable, but that did not work because my variable was still true. I suspect that there exist two .NET references to the same Android View and only one of them gets disposed but the other one is hanging around not knowing that the underlying object has gone. I don't know, how to check for Android View existence from C# code, though.

  • midixmidix LVMember ✭✭

    Ok, I kinda give up, this is the only thing that worked:

            protected override void OnAttachedToWindow()
            {
                // I gave up and added this ugly catch
                // because:
                // - the Element is not null, 
                // - the native Handle is not null,
                // - Dispose() has not been called yet,
                // but still the base crashed with ObjectDisposedException
                // when it tried to read Context of Android View
                try
                {
                    base.OnAttachedToWindow();
                }
                catch(ObjectDisposedException) { }
            }
    
  • AshleyGazichAshleyGazich USMember, Xamarin Team Xamurai

    @midix

    This thread is quite old and may not be relevant for the issue you are having. Bug 42678 is marked resolved fixed; I tested the sample provided in the report’s description with Xamarin latest Stable on device and emulator using both Xamarin.Forms latest stable and the current prerelease packages, and was unable to replicate a crash. If you haven’t already done so, please try the prerelease package to see if that helps. Should the issue persist and you happen to have a chance to put one together, a bug report for your issue might be worth considering. Thanks!

  • lucidBrotlucidBrot Member ✭✭

    For me, the reason was that the object was disposed but there was still an event listener that would access it on the event.
    Fix:

    protected override void Dispose(bool disposing)
            {
                Element.PropertyChanged -= MyEventListenerCallback;
                base.Dispose(disposing);
            }
    
Sign In or Register to comment.