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

Android: MvxBindableListItemView instances leaking #17

Closed
danielcweber opened this issue Jun 27, 2012 · 58 comments
Closed

Android: MvxBindableListItemView instances leaking #17

danielcweber opened this issue Jun 27, 2012 · 58 comments
Labels
t/bug Bug type

Comments

@danielcweber
Copy link
Contributor

Hello,

consider the following layout:

-cirrious.mvvmcross.binding.android.views.MvxBindableLinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
local:MvxItemTemplate="@layout/itemlayout"
local:MvxBind="{'ItemsSource':{'Path':'Items'}}" />

where "Items" will be an object implementing INotifyCollectionChanged and "itemlayout" is

-FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res/MyApp"
android:layout_width="44dp"
android:layout_height="wrap_content">
-TextView
android:background="#ffdddddd"
android:layout_width="40dp"
android:layout_height="28dp"
android:textColor="#ff000000"
android:gravity="center"
local:MvxBind="{'Text':{'Path':'ID'}}/>
-/FrameLayout-

then I found the created instances of MvxBindableListItemView (and the contained FrameLayout and TextView) to be leaking. Moreover, MvxJavaContainer instances leak in roughly the same number.

There is no leaking when the itemlayout is not bound.

What I found out using the Dalvik Debug Monitor and the Eclipse Memory Analyzer ist that all MvxBindableListItemView, FrameLayout and TextView instances are marked as residing on the native stack, they mainly hold circular references to another (with some ColorDrawable instances in between). I couldn't really find any Java-Class holding to all that instances so I assume the references are kept alive by the binding on the .net-side (with MvxJavaContainer possibly bridging the two sides).

@slodge
Copy link
Contributor

slodge commented Jun 27, 2012

Thanks Blewzman

Have you got any more info on what you are seeing?

I'm not 100% sure what you are asking about. A LinearLayout is
non-virtualised by design - if you want cell reuse, then you need to use a
ListView. Or is there something else happening here? e.g. are the views not
being destroyed when the activity is destroyed?

Is there a complete project I can see that illustrates the issue you are
currently seeing?

Thanks

Stuart

On 27 June 2012 17:48, Blewzman <
reply@reply.github.com

wrote:

Hello,

consider the following layout:

<cirrious.mvvmcross.binding.android.views.MvxBindableLinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
local:MvxItemTemplate="@layout/itemlayout"
local:MvxBind="{'ItemsSource':{'Path':'Items'}}" />

where itemlayout is


<TextView
android:background="#ffdddddd"
android:layout_width="40dp"
android:layout_height="28dp"
android:textColor="#ff000000"
android:gravity="center"
local:MvxBind="{'Text':{'Path':'ID'}/>


Reply to this email directly or view it on GitHub:
#17

@danielcweber
Copy link
Contributor Author

Hi Stuart,

thanks for the quick response. I see my description of the issue is lacking. In my app, the bindable LinearLayout of your framework is bound (through ItemsSource) to a collection that implements INotifyCollectionChanged. During the lifetime of the app, this collection will grow and shrink. The LinearLayout reflects this perfectly - there is always the correct number of child-views visible and each of these childviews is bound correctly to the item of the collection.

However, when the collection changes and the LinearLayout is refilled, the old views are removed from the LinearLayout but they remain referenced by something and this is what leaks. So even if the number of items in the source collection only varies from 0-10, i will end up with hundreds of MvxBindableListItemViews. Full Garbage Collection eventually kicks in every two seconds until it blows up.

If I remove the binding of the child-views (a simple binding of some string to the Text-Property) there are no leaks. So I suspect the binding is not disposed of when the child view is disposed of (if it is at all - during refill of the LinearLayout)

Activities do not play a role in this scenario, there is always the same activity displayed.

There is currently no complete project but I'm working on that.

P.S. In my original post the layout got screwed up because of the XML tags. I replaced "<" by "-", I hope it makes sense for the moment.

Blewzman

@slodge
Copy link
Contributor

slodge commented Jun 27, 2012

That sounds plausible

Thanks for the longer explanation.

Currently on a train, but just prototyped some code.

Will send when I get home (tethering arrives on my lumia any day now...)

Thanks again. If this is the bug you suggest then I suspect it also
occurs in lists - but will be less obvious there because of the reuse.

Stuart

Sent from my Windows Phone
From: Blewzman
Sent: 27/06/2012 19:01
To: Stuart Lodge
Subject: Re: [MvvmCross] Android: MvxBindableListItemView instances
leaking (#17)
Hi Stuart,

thanks for the quick response. I see my description of the issue is
lacking. In my app, the bindable LinearLayout of your framework is
bound (through ItemsSource) to a collection that implements
INotifyCollectionChanged. During the lifetime of the app, this
collection will grow and shrink. The LinearLayout reflects this
perfectly - there is always the correct number of child-views visible
and each of these childviews is bound correctly to the item of the
collection.

However, when the collection changes and the LinearLayout is refilled,
the old views are removed from the LinearLayout but they remain
referenced by something and this is what leaks. So even if the number
of items in the source collection only varies from 0-10, i will end up
with hundreds of MvxBindableListItemViews. Full Garbage Collection
eventually kicks in every two seconds until it blows up.

If I remove the binding of the child-views (a simple binding of some
string to the Text-Property) there are no leaks. So I suspect the
binding is not disposed of when the child view is disposed of (if it
is at all - during refill of the LinearLayout)

Activities do not play a role in this scenario, there is always the
same activity displayed.

There is currently no complete project but I'm working on that.


Reply to this email directly or view it on GitHub:
#17 (comment)

@slodge
Copy link
Contributor

slodge commented Jun 27, 2012

Out of interest, why are you using a linearlayout rather than a list?
Is it just for additional control over what gets scrolled?

Sent from my Windows Phone
From: Blewzman
Sent: 27/06/2012 19:01
To: Stuart Lodge
Subject: Re: [MvvmCross] Android: MvxBindableListItemView instances
leaking (#17)
Hi Stuart,

thanks for the quick response. I see my description of the issue is
lacking. In my app, the bindable LinearLayout of your framework is
bound (through ItemsSource) to a collection that implements
INotifyCollectionChanged. During the lifetime of the app, this
collection will grow and shrink. The LinearLayout reflects this
perfectly - there is always the correct number of child-views visible
and each of these childviews is bound correctly to the item of the
collection.

However, when the collection changes and the LinearLayout is refilled,
the old views are removed from the LinearLayout but they remain
referenced by something and this is what leaks. So even if the number
of items in the source collection only varies from 0-10, i will end up
with hundreds of MvxBindableListItemViews. Full Garbage Collection
eventually kicks in every two seconds until it blows up.

If I remove the binding of the child-views (a simple binding of some
string to the Text-Property) there are no leaks. So I suspect the
binding is not disposed of when the child view is disposed of (if it
is at all - during refill of the LinearLayout)

Activities do not play a role in this scenario, there is always the
same activity displayed.

There is currently no complete project but I'm working on that.


Reply to this email directly or view it on GitHub:
#17 (comment)

@danielcweber
Copy link
Contributor Author

Great, thanks.

I need to place the child views horizontally, I tried with list but that was only getting me a vertical layout (with bars between boundaries, which is fine for other activities in my app).

@slodge
Copy link
Contributor

slodge commented Jun 27, 2012

Here's an attempt at a Dispose() implementation.

Not got a full repro case here so can't test this properly

It looks ok... but definitely needs some testing - I don't even know if the Dispose() gets correctly called right now.

Stuart

#region Copyright
// <copyright file="MvxBindableListItemView.cs" company="Cirrious">
// (c) Copyright Cirrious. http://www.cirrious.com
// This source is subject to the Microsoft Public License (Ms-PL)
// Please see license.txt on http://opensource.org/licenses/ms-pl.html
// All other rights reserved.
// </copyright>
// 
// Project Lead - Stuart Lodge, Cirrious. http://www.cirrious.com
#endregion

using System.Collections.Generic;
using Android.Content;
using Android.Views;
using Android.Widget;
using Cirrious.MvvmCross.Binding.Android.Interfaces.Views;
using Cirrious.MvvmCross.Binding.Interfaces;

namespace Cirrious.MvvmCross.Binding.Android.Views
{
    public class MvxBindableListItemView
        : FrameLayout
        , IMvxBindableListItemView
    {
        private readonly View _content;
        private readonly IMvxBindingActivity _bindingActivity;

        private readonly int _templateId;

        public MvxBindableListItemView(Context context, IMvxBindingActivity bindingActivity, int templateId, object source)
            : base(context)
        {
            _templateId = templateId;
            _bindingActivity = bindingActivity;
            _content = _bindingActivity.BindingInflate(source, templateId, this);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _bindingActivity.ClearBindings(this);
            }

            base.Dispose(disposing);
        }

        protected View Content { get { return _content; } }

        #region IMvxBindableListItemView Members

        public int TemplateId
        {
            get { return _templateId; }
        }

        public virtual void BindTo(object source)
        {
            Dictionary<View, IList<IMvxUpdateableBinding>> bindings;
            if (!TryGetJavaBindingContainer(out bindings))
            {
                return;
            }

            foreach (var binding in bindings)
            {
                foreach (var bind in binding.Value)
                {
                    bind.DataContext = source;
                }
            }
        }

        private bool TryGetJavaBindingContainer(out Dictionary<View, IList<IMvxUpdateableBinding>> result)
        {
            result = null;

            if (_content == null)
            {
                return false;
            }

            var tag = _content.GetTag(MvxAndroidBindingResource.Instance.BindingTagUnique);
            if (tag == null)
            {
                return false;
            }


            var wrappedResult = tag as MvxJavaContainer<Dictionary<View, IList<IMvxUpdateableBinding>>>;
            if (wrappedResult == null)
            {
                return false;
            }

            result = wrappedResult.Object;
            return true;
        }

        #endregion
    }
}

@danielcweber
Copy link
Contributor Author

Thanx! I will add your implementation of Dispose to my checkout of MvvmCross tomorrow and see whether it is called at all and whether it solves the problem.

Daniel

@danielcweber
Copy link
Contributor Author

Hi Stuart,

I did a quick test. First I just implemented the Dispose method on MvxBindableListItemView. Unfortunately, it is not called when the MvxBindableListItemView is removed from a ViewGroup (LinearLayout in this case). So a call to RemoveAllViews obviously will not dispose the children. I then did a reimplementation of MvxBindableLinearLayoutExtensions.Refill:


    public static class MvxBindableLinearLayoutExtensions
    {
        public static void Refill (this LinearLayout layout, IAdapter adapter)
        {
            var count = layout.ChildCount;
            for (var i = 0; i < count; i++)
            {
                var child = layout.GetChildAt(i);
                child.Dispose ();
            }

            layout.RemoveAllViews();
            count = adapter.Count;
            for (var i = 0; i < count; i++)
            {
                layout.AddView(adapter.GetView(i, null, layout));
            }            
        }
    }

The problem with this solution is obviously that it is specifically for the LinearLayout only and that code is likely to be repeated - not very nice.

I have not tested a binding on the childview yet, so I don't know yet whether disposing the child view solves the leaking problem. I will test that later today.

Daniel

@slodge
Copy link
Contributor

slodge commented Jun 28, 2012

Thanks for the info.

Just to check: your Refill rewrite - did it actually stop the leak?

I'm confident the bound views within a child activity should get cleared - because the Activity clears all bindings using ClearBoundViews inside its OnDestroy handler

However, for the situation you are currently using, then the list of bound views inside the activity could get rather large - so it will chew memory as you've discovered.

The problem of repeating code is a general one in Mvx - never before have I wanted mixins or multiple inheritance quite so much in C#!

As an alternative to your Refill rewrite another solution might be to use a ChildViewRemoved hook inside the LinearLayout code -

            // hooked up in the constructor as:
            this.ChildViewRemoved += OnChildViewRemoved;

        private void OnChildViewRemoved(object sender, ChildViewRemovedEventArgs childViewRemovedEventArgs)
        {
            var boundChild = childViewRemovedEventArgs.Child as MvxBindableListItemView;
            if (boundChild != null)
            {
                boundChild.ClearBindings();
            }
        }

with the list item code rewritten to include:

        public void ClearBindings()
        {
            _bindingActivity.ClearBindings(this);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                ClearBindings();
            }

            base.Dispose(disposing);
        }

Well found and diagnose on this problem so far. I've only used the LinearLayout binding for a very small amount of work so far - and that was in quite a static situation - for most other situations I've used lists and there the cell reuse helps with limit the memory consumption - although I'd be quite happy to add the ChildViewRemoved code there too (after some testing!)

When we get somewhere we're both vaguely happy with I'll definitely push some fixes for this to the tree - thanks for the investigation on this!

Stuart

@slodge
Copy link
Contributor

slodge commented Jun 28, 2012

I take that last post back. For ListView we wouldn't want to do the exact same code during the ChildRemoved handler - instead we'd want to consider calling BindTo(null) in that handler - in the list, we don't want to wipe the bindings but rather to clear the binding source.

... would definitely needs testing on a few different android versions just to make sure I understand it :)

@danielcweber
Copy link
Contributor Author

Hi Stuart,

good news: Calling Dispose on MvxBindableListItemView stops the leak! I only tested my Refill-rewrite and not the ChildViewRemoved-Handler.

I would like to contribute to the project, particularly in solvin this issue. One question: How crucial is it to utilize an Adapter class? I mean, does the code at some place depend on the LinearLayout using an Adapter internally? I have written a bindable LinearLayout-like class in the past for the Compact Framework, it handles INotifyCollectionChanged-events on a finer level (just replacing the views where neccessary and not refilling the entire Layout). It handles everything itself, does not depend on any Adapter and can be bound to objects implementing IEnumerable or INotifyCollectionChanged.

@slodge
Copy link
Contributor

slodge commented Jun 28, 2012

How crucial is it to utilize an Adapter class?

Not crucial, but there is code that can be reused there.

Thinking out loud...

The reason I used an adapter originally was to allow code sharing between
lists, autocompletes, linear layouts, etc. The adapter was already written
to work with IEnumerable and INotifyCollectionChanged, and it already had
GetBindableView - so it was really a case of telling people that they could
use their same specialised Adapter code for both Lists, and for
LinearLayouts.

One reason I used such limited INotifyCollectionChanged support was because
at some point (maybe pre-Mango on WP7) the only
NotifyCollectionChangedAction value available seemed to be Reset - but I
think that's much more open now :)

Another reason I used such limited actions was because the default Android
list doesn't do anything clever with list changes and because the apps I
was working on didn't really require it...

If you wanted to write a linearlayout that took note of the actions (or
even a full binding list that did the same thing!) then I'd love to use it
either within Mvx or by dragging it in from some external library :)

You could probably do this while still using the existing adapter if you
wanted to - should just be the case of changing the event fired from the
adapter to the linearlayout - i.e. by making the event fired on from
MvxBindableListAdapterWithChangedEvent.cs a little more verbose?

Stuart

P.S. Sorry for the rambling answer and for my MvxFarTooLongClassNames!

@danielcweber
Copy link
Contributor Author

I have a quick attempt at a bindable items-View that should be reusable for every kind of ViewGroup-Layout. I post it here, I hope the formatting doesn't get screwed up:


//MvxBindableItemsView is just a FrameLayout parametrized on a ViewGroupType. So it does
    //not matter here whether in the end we use a LinearLayout or any other ViewGroud-derived View.
    public abstract class MvxBindableItemsView<TViewGroup> : FrameLayout where TViewGroup : ViewGroup where TViewGroup : new()
    {
        private IEnumerable itemsSource;
        private TViewGroup innerViewGroup;
        private int itemTemplateId;

        protected MvxBindableItemsView(Context context, IAttributeSet attrs) :  base(context, attrs)
        {
            //Read the itemTemplateID, we could as well add a public setter (see below).
            this.itemTemplateId = MvxBindableListViewHelpers.ReadTemplatePath(context, attrs);
            
            //Instantiate an instance of TViewGroup and add it to the FrameLayout.
            this.innerViewGroup = new TViewGroup();
            //TODO: Set width and height to fill_parent?
            this.AddView(this.innerViewGroup);
        }

        #region ItemsSource_CollectionChanged event handler
        private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case (NotifyCollectionChangedAction.Add):
                {
                    int startingIndex = e.NewStartingIndex;

                    //We can even handle more than 1 new item.
                    for (int i = 0; i < e.NewItems.Count; i++)
                    {
                        //TODO: Use an AddView overload that takes LayoutParams? What use has it?
                        this.innerViewGroup.AddView(this.CreateAndBindItemView(e.NewItems[i]), (startingIndex + i));
                    }

                    break;
                }
                case (NotifyCollectionChangedAction.Move):
                {
                    throw new NotImplementedException("don't move items or implement this.");
                }
                case (NotifyCollectionChangedAction.Remove):
                {
                    if (e.OldStartingIndex == -1)
                        throw new ArgumentException();

                    for (int i = 0; i < e.OldItems.Count; i++)
                    {
                        View view = ((TViewGroup)this.innerViewGroup).GetChildAt(e.OldStartingIndex);
                        this.innerViewGroup.RemoveViewAt(e.OldStartingIndex);

                        //Dispose the view to release the binding
                        if (view != null)
                            view.Dispose();
                    }

                    break;
                }
                case (NotifyCollectionChangedAction.Replace):
                {
                    if (e.NewStartingIndex == -1)
                        throw new ArgumentException();

                    for (int i = 0; i < e.NewItems.Count; i++)
                    {
                        View oldView = this.innerViewGroup.GetChildAt(e.NewStartingIndex + i);
                        this.innerViewGroup.RemoveViewAt(e.NewStartingIndex + i);

                        //Dispose the view to release the binding
                        if (oldView != null)
                            oldView.Dispose();

                        this.innerViewGroup.AddView(this.CreateAndBindItemView(e.NewItems[i]), (e.NewStartingIndex + i));
                    }

                    break;
                }
                case (NotifyCollectionChangedAction.Reset):
                {
                    //Reset: Dispose all ChildViews of the inner ViewGroup. Alternatively, dispose
                    //the while innerViewGroup and create a new one. Then refill.
                    this.ClearInnerViewGroup();
                    this.FillInnerViewGroup();
                    break;
                }
            }
        }
        #endregion

        protected override void Dispose(bool disposing)
        {
            //TODO: I don't currently know whether base.Dispose disposes all child Views.
            //In this case this override is useless.
            if (disposing)
                this.innerViewGroup.Dispose();

            base.Dispose(disposing);
        }

        private void ClearInnerViewGroup()
        {
            //Important: Dispose all child views in innerViewGroup.
            var childCount = this.innerViewGroup.ChildCount;
            for (int i = 0; i < childCount; i++)
            {
                this.innerViewGroup.GetChildAt(i).Dispose();
            }

            //TODO: Are the child views already removed by Dispose() ?
            this.innerViewGroup.RemoveAllViews();
        }

        private void FillInnerViewGroup()
        {
            //Fill the innerViewGroup from itemsSource.
            if (this.itemsSource != null)
            {
                int index = 0;
                foreach (object obj in this.itemsSource)
                {
                    this.innerViewGroup.AddView(this.CreateAndBindItemView(obj), index);
                    index++;
                }
            }
        }

        private MvxBindableListItemView CreateAndBindItemView(object source)
        {
            //TODO: View creation and binding like it is done now in MvxBindableListAdapter.
            throw new NotImplementedException();
        }

        //ItemTemplateID is not settable - there may or may not be scenarios in which it should
        //be dynamic, but for the moment...
        public int ItemTemplateId
        {
            get
            {
                return this.itemTemplateId;
            }
        }

        #region ItemsSource property
        public IEnumerable ItemsSource
        {
            get
            {
                return this.itemsSource;
            }
            set
            {
                if (value != this.itemsSource)
                {
                    if ((this.itemsSource != null) && (this.itemsSource is INotifyCollectionChanged))
                        ((INotifyCollectionChanged)this.itemsSource).CollectionChanged -= new NotifyCollectionChangedEventHandler(this.ItemsSource_CollectionChanged);

                    this.ClearInnerViewGroup();
                    this.itemsSource = value;

                    if (this.itemsSource != null)
                    {
                        if (this.itemsSource is INotifyCollectionChanged)
                            ((INotifyCollectionChanged)this.itemsSource).CollectionChanged += new NotifyCollectionChangedEventHandler(this.ItemsSource_CollectionChanged);

                        this.FillInnerViewGroup();
                    }
                }
            }
        }
        #endregion
    }

@danielcweber
Copy link
Contributor Author

Specialization on e.g. LinearLayout can then be done by:


public class MvxBindableLinearLayoutEx : MvxBindableItemsView<LinearLayout>
{
    public MvxBindableLinearLayoutEx(Context context, IAttributeSet attrs) :  base(context, attrs)
    {

    }
}

@danielcweber
Copy link
Contributor Author

I just saw that the code in the last posting will not compile because LinearLayout does not have a public parameterless constructor. Nevermind, we can always take the generic parameter out of the code and put a protected abstract method "CreateItemsViewGroup" (or something like that) into MvxBindableItemsView.

@slodge
Copy link
Contributor

slodge commented Jun 28, 2012

Yeah - thanks - general approach looks like the right sort of thing

I guess Move isn't too hard to do either...

I also need to think and/or readup about calling Dispose on Android Views -
and I do think that we definitely should remove the Views from the
ViewGroup before we call Dispose on them (just feels odd to Dispose them
and to then call code which might use them...)

I'm focussing on the day job right now, but wondering about carrying on
using the adapter - by using an approach like
https://gist.github.com/3011980 - obviously that approach has a TODO in it

  • which is basically getting your switch statement code across.

I think a third way might be the best approach... something that separates
out both the property/collection subscription and the View creation away
from the Adapter into some new object - that might help encourage maximum
code reuse - will think about it...

Thanks again for giving me something fun to think about today :)
Stuart

@slodge
Copy link
Contributor

slodge commented Jun 28, 2012

I wonder if this would work with a map view and pushpins...

On 28 June 2012 16:02, Blewzman <
reply@reply.github.com

wrote:

Specialization on e.g. LinearLayout can then be done by:


public class MvxBindableLinearLayoutEx : MvxBindableItemsView
{
   public MvxBindableLinearLayoutEx(Context context, IAttributeSet attrs)
:  base(context, attrs)
    {

   }
}


Reply to this email directly or view it on GitHub:
#17 (comment)

@danielcweber
Copy link
Contributor Author

If we really want to keep the adapter, why not let the adapter maintain a list of the Views it created, so it establishes its own 1:1 relationship between sourceObjects and Views and can dispose the views accordingly. However, I somehow feel that the method


public override View GetView(int position, View convertView, ViewGroup parent)

suggests that somehow the user of the Adapter is responsible for maintaining the views (which is exactly what a ViewGroup does), so there's not much separation of concern here....

Your idea of a completely different class to handle is good - basically what we need here is some kind of projection from a list of some type T to a list of type View - like the LINQ Select-method on IEnumerable, just not on IEnumerable but on INotifyCollectionChanged / ObservableCollection. Once we have projected the source collection s to a View-collection c, MvxBindableItemsView could easily subscribe to INotifyCollectionChanged on c and then just insert and remove the views as they come.

@slodge
Copy link
Contributor

slodge commented Jun 28, 2012

the user of the Adapter is responsible for maintaining the views

I think this is exactly right. It's exactly what an Android ListView does.
It owns a small set of Views which it asks the Adapter to create and
which it then later passes back to the Adapter for recycling.

The LinearLayout (or MvxBindableItemsView) will maintain a 1:1 list, but
obviously that's not scalable for large lists... and it's not what most
situations will use (I think!)

In terms of separation of concerns, it's not perfect, but it is the Android
pattern for lists (and autocompletes and...) - and I definitely need to
support Adapters for normal ListViews.

Will have a proper play when I get a chance...

Stuart

@slodge
Copy link
Contributor

slodge commented Jun 28, 2012

I hacked again to produce the files: https://gist.github.com/3012791

These 3 files compile... but I really need to step back and build a test harness which inserts, removes, replaces and moves within a list :)

Beyond this first code:

  • I really like your idea of using a generic so that the code can be used with multiple ViewGroups - but we'll need concrete (non generic) names to use in the xml
  • I quite like the idea of separating out the view creation code from the Adapter - although the GetView method will need to stay in the Adapter in order for them to satisfy the native Android ListViews
  • I'm sure there's heaps more fun we could have if we get this code as reusable as possible...

Thanks again for the original problem report and for the further thought and investigation you've put in :)

@danielcweber
Copy link
Contributor Author

...but we'll need concrete (non generic) names to use in the xml

Definitely, eventually for every ViewGroup derived type there would have to be a type that inherits from the generic type (like in "class MvxBindableLinearLayoutEx : MvxBindableItemsView").

...although the GetView method will need to stay in the Adapter in order for them to satisfy the native Android ListViews

Just had a quick look into the docs - how can ListView possibly react to Adapter changes on a level as fine as we are just trying to - after all, there's only "NotifyDataSetChanged" and that doesn't give too much information.

Given that ListView derives from ViewGroup, I wonder if ListView could be used without any Adapter at all, just by our generic approach. Of course, the user of the ListView should not be able to set an Adapter manually then since this would mess up all logic.

@slodge
Copy link
Contributor

slodge commented Jun 29, 2012

...although the GetView method will need to stay in the Adapter in order
for them to satisfy the native Android ListViews

Just had a quick look into the docs - how can ListView possibly react to
Adapter changes on a level as fine as we are just trying to - after all,
there's only "NotifyDataSetChanged" and that doesn't give too much
information.

Given that ListView derives from ViewGroup, I wonder if ListView could be
used without any Adapter at all, just by our generic approach. Of course,
the user of the ListView should not be able to set an Adapter manually then
since this would mess up all logic.

I think that could be done - but rewriting the listview might be a future
project rather than one for now :) Until then, ListViews have to stay as
they are and they have to use Adapters via GetView - it's the Android way...

As a first step, I just want to focus on getting both the listview and the
linearlayout working without leaking and without doing too much copy and
paste.

Stuart

@danielcweber
Copy link
Contributor Author

Alright. If you need some coding done by me let me know. Maybe I do my own experiments with the generic approach and see how it works with the ListView and whether it is promising at all. I'll file another issue later for which I have already found a solution.

Daniel

@slodge
Copy link
Contributor

slodge commented Jul 6, 2012

Hi Daniel

I've pushed a version which I hope doesn't leak.... but I don't really have a good test case to verify it.

The version I've gone for uses an adapter - my reason for this is so that it can share the core data source binding code with BindableListView, BindableAutoComplete and BindableSpinner.

Currently, I've not made the LinearLayout inherit from a generic base class - just because I'm out of time right now and because I personally don't have a use for it right now. However, I'd be very happy for you to contribute a generic base class implementation if you need one - but I would prefer that to use the adapter if it can... happy to discuss this if you're interested (it might be easier to discuss "live" on http://jabbr.net/#/rooms/mvvmcross rather than on here)

Hope the memory fix works for you - thanks again for taking the time to report the bug and for all your assistance in investigating it

Stuart

@danielcweber
Copy link
Contributor Author

Hi Stuart,

thanks for the updates. I'd be happy to contribute some code to your project. I also see that the ListView and some other Views will not do anything useful without an adapter. The bad thing about it is that it leads to some repetitive code, and all the bindable views hide the base class implementation of the Adapter by the 'new' keyword, which is not really satisfying either. Then, we have ViewGroups that could be bindable but do not have any adapter at all (such as LinearLayout). Very complicated. I just realized how I love the WPF/Silverlight approach of binding any type of composite view (Panel) to any list by the use of ItemsControl. I will think about a solution.

@danielcweber
Copy link
Contributor Author

Hi Stuart,

me again. I haven't yet had the time to come up with a re-implementation and abstraction of the bindable lists, but I investigated another memory leak. Suppose we have a ViewModel:


using System.Linq;
public sealed class DebugViewModel
{
    public DebugViewModel()
    {
        this.Items = Enumerable.Range(1, 100).Select(x => new DebugViewModelItem(x.ToString())).ToArray();
    }
    public DebugViewModelItem[] Items
    {
        get;
        private set;
    }
}

which exposes a list of items of type DebugViewModelItem:


public sealed class DebugViewModelItem : INotifyPropertyChanged
{
    private PropertyChangedEventHandler propertyChangedEventHandler;
    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            lock (this)
            {
                System.Diagnostics.Debug.WriteLine("EventHandler added.");
                this.propertyChangedEventHandler += value;
            }
        }
        remove
        {
            lock (this)
            {
                System.Diagnostics.Debug.WriteLine("EventHandler removed.");
                this.propertyChangedEventHandler -= value;
            }
        }
    }
    public DebugViewModelItem(string value)
    {
        this.Value = value;
    }
    public string Value
    {
        get;
        private set;
    }
}

These are bound by the following Layouts:

DebugLayout.xaml:


<?xml version="1.0" encoding="utf-8"?>
<cirrious.mvvmcross.binding.android.views.MvxBindableLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res/MyApp"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    local:MvxItemTemplate="@layout/debugitemlayout"
    local:MvxBind="{'ItemsSource':{'Path':'Items'}}" />

DebugItemLayout.xaml


<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res/MyApp"
    android:layout_width="40dp"
    android:layout_height="40dp"
    local:MvxBind="{'Text':{'Path':'Value'}}" />

Load these into the following Activity:


[Activity(MainLauncher = true,
            Label = "Debug Activity")]
public class DebugActivity : MvxSimpleBindingActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        this.ViewModel = new DebugViewModel();
        this.SetContentView(Resource.Layout.DebugLayout);
        new Handler().PostDelayed(() =>
        {
            this.StartActivity(typeof(DebugActivity2));
            this.Finish();
        }, 120 *1000);
    }
    protected override void OnDestroy()
    {
        System.Diagnostics.Debug.WriteLine("DebugActivity.OnDestroy");
        base.OnDestroy();
    }
}

The activity loads DebugLayout, and after 2 minutes opens another empty activity not given here and finishes itself. Here's what happens:

The activity is bound correctly and "EventHandler added" is output 100 times.
After 2 minutes, DebugActivity.OnDestroy is called (and the corresponding debug message is output) and clears the bindings on the activity.

We would now expect "EventHandler removed." to be output another 100 times, this does not happen. Unless the ViewModel goes out of scope, it keeps a reference to the binding (through the PropertyChangedEventHandler field). I found out that the bindings on the items are not stored in the tag-property of the activity but elsewehere (I think in the BindableLinearLayout) and they aren't cleared. An approach to implement or override Dispose everywhere does not work because Android does not bother disposing all the child-Views when an Activity is destroyed. I finally added some code in MvxBindingActivityView:

In MvxBindingActivityView.ClearBindings(View view), append


var viewGroup = view as ViewGroup;
if (viewGroup != null)
{
    for (int i = 0; i < viewGroup.ChildCount; i++)
    {
        this.ClearBindings(viewGroup.GetChildAt(i));
    }
}

This is a rather brute force approach, it works for me but I don't know if this does always what the user intended.

In MvxBindingActivityView.ClearBoundViews, replace all code with


_boundViews.ForEach(this.ClearBindings);

I also wonder what happens when a BindableLayout instance is not in _boundViews but has bindings itself (is that even possible?).

What do you think ?

Best regards,
Daniel

@slodge
Copy link
Contributor

slodge commented Sep 12, 2012

Thanks Daniel.

From what you are saying, the bound controls are somehow not getting added to _boundViews - whichs sounds strange (sounds like you've found another bug!)

the bindings on the items are not stored in the tag-property of the activity but elsewehere

That's correct - the binding should be stored in the tag of the bound view (whatever was inflated) - but that view should then get added to _boundViews collection - so the bindings should get cleared up either when OnDestroy() or Dispose() happens - whichever happens first!

I'll run the code....

... and ....

Good find - that's a serious leak!

The bug is in:

        public View BindingInflate(int resourceId, ViewGroup viewGroup)
        {
            var view = BindingInflate(DefaultBindingSource, resourceId, viewGroup);
            if (view != null)
                _boundViews.Add(view);
            return view;
        }

        public View BindingInflate(object source, int resourceId, ViewGroup viewGroup)
        {
            return CommonInflate(
                resourceId,
                viewGroup,
                (layoutInflator) => new MvxBindingLayoutInflatorFactory(source, layoutInflator));
        }

When the second inflate method is used, then the _boundViews.Add(view); should also be called - oops

Will fix with:

        public View BindingInflate(int resourceId, ViewGroup viewGroup)
        {
            var view = BindingInflate(DefaultBindingSource, resourceId, viewGroup);
            return view;
        }

        public View BindingInflate(object source, int resourceId, ViewGroup viewGroup)
        {
            var view = CommonInflate(
                resourceId,
                viewGroup,
                (layoutInflator) => new MvxBindingLayoutInflatorFactory(source, layoutInflator));
            if (view != null)
                _boundViews.Add(view);
            return view;
        }

Sorry for the bug

Thanks HUGELY for the report!

Stuart

@danielcweber
Copy link
Contributor Author

Hi Stuart,

thanks for the fix. I found another potential leak. Suppose we have a ObservableCollection which is bound to the ItemsSource property of a MvxBindableListView. Upon disposal of the Binding, the ItemsView property is not set to null, meaning that the ObservableCollection will keep a NotifyCollectionChangedEventHandler-delegate to the MvxBindableListView (more specifically to its Adapter). The code in my last post can be used to reproduce this, with some changes:

Create a ObservableCollection-derived class for debug output:


public sealed class ObservableCollectionImpl : ObservableCollection
{
    private NotifyCollectionChangedEventHandler collectionChangedEventHandler;
    public override event NotifyCollectionChangedEventHandler CollectionChanged
    {
        add
        {
            lock (this)
            {
                System.Diagnostics.Debug.WriteLine("NotifyCollectionChangedEventHandler added.");
                this.collectionChangedEventHandler += value;
            }
        }
        remove
        {
            lock (this)
            {
                System.Diagnostics.Debug.WriteLine("NotifyCollectionChangedEventHandler removed.");
                this.collectionChangedEventHandler -= value;
            }
        }
    }
    //override OnCollectionChanged if needed
    
    public ObservableCollectionImpl(IEnumerable collection) : base(collection)
    {
    }
}

Change DebugViewModel:


public sealed class DebugViewModel
{
    public DebugViewModel()
    {
        this.Items = new ObservableCollectionImpl(Enumerable.Range(1, 100).Select(x => new DebugViewModelItem(x.ToString())));
    }
    public ObservableCollection Items
    {
        get;
        private set;
    }
}

If you run this code, "NotifyCollectionChangedEventHandler added" will be output, but "NotifyCollectionChangedEventHandler removed" won't when the activity is destroyed.

I tried to find out how this is handled in WPF but couldn't find documentation on how the target is updated when the binding is disposed. I did a quick workaround:


private sealed class SetNullOnDisposeBindingFactory : MvxCustomBindingFactory where TTarget : class
{
    #region SetNullOnDisposeBinding
    private sealed class SetNullOnDisposeBinding : MvxBaseAndroidTargetBinding
    {
        private MvxPropertyInfoTargetBinding baseBinding;
        public SetNullOnDisposeBinding(TTarget view, string propertyName)
        {
            if (view == null)
                throw new ArgumentNullException();
            var propertyInfo = view.GetType().GetProperty(propertyName);
            if (propertyInfo == null)
                throw new ArgumentException();
            this.baseBinding = new MvxPropertyInfoTargetBinding(view, propertyInfo);
        }
        public override void SetValue(object value)
        {
            if (this.baseBinding != null)
                this.baseBinding.SetValue(value);
        }
        protected override void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                if (this.baseBinding != null)
                {
                    this.baseBinding.SetValue(null);
                    this.baseBinding.Dispose();
                    this.baseBinding = null;
                }
            }
            base.Dispose(isDisposing);
        }
        public override Type TargetType
        {
            get
            {
                return typeof(object);
            }
        }
        public override MvxBindingMode DefaultMode
        {
            get
            {
                return MvxBindingMode.OneWay;
            }
        }
    }
    #endregion
    public SetNullOnDisposeBindingFactory(string propertyName) : base(propertyName, (view) => new SetNullOnDisposeBinding(view, propertyName))
    {
    }
}

This is then registered with


registry.RegisterFactory(new SetNullOnDisposeBindingFactory("ItemsSource"));

The code has flaws - for example I don't know whether using MvxPropertyInfoTargetBinding as baseBinding is always justified, but it works for now.

Daniel

@slodge
Copy link
Contributor

slodge commented Sep 13, 2012

Interesting (again!)

Would need to think on this one.

For "static pages" (normal activities) I guess there wouldn't be a leak as both View and ViewModel should disappear off together into the deadpool, taking the collection with them, but I can imagine there might be a leak for pages which load/switch panels dynamically into place.

WPF might be able to cheat - it might be able to hook into the destroy event for the control to unhook all event listeners. I guess we could similary just hook into Dispose() on the ListView - but I'm not confident on when that gets called.

Will need to think about this one.

I guess instead of the "setting to null" solution we could add some sort of ClearListeners(string propertyName) mechanism on all bound views - that could be called by all binding disposals and would let the view know to clear their own event subscriptions...

Definitely need to think on this one...

Thanks yet again!

Stuart

@danielcweber
Copy link
Contributor Author

I think I have a good solution to this problem. Are you familiar with weak events? I wrote a simple but working implementation of a WeakEventManager (a concept present in WPF too) that would handle all the memory leaks coming from un-unsubscribed events.

@slodge
Copy link
Contributor

slodge commented Sep 14, 2012

I'm a bit familiar with Weak references - I've used them indirectly in MvvmLight (it's messenger is weak reference based, probably for very similar reasons) - http://bit.ly/PAmzau

I considered using WeakRefs in the binding originally - but shied away from it because I thought we could keep control without them.

Is your prototype up anywhere? Would be interested to play with it

Before using them, I think we should discuss it through with users... e.g. I might asking @lbugnion about what negatives (if any) they've caused in mvvmlight.

Always interesting :)

@danielcweber
Copy link
Contributor Author

Its not up anywhere, having just written it. Can I upload it anywhere here? I'm not too familiar with Git or Github though...

@slodge
Copy link
Contributor

slodge commented Sep 14, 2012

Quickest way is probably the new http://windows.github.com/ tool.

I'm still mainly using http://gitextensions.googlecode.com/. A 2 minute guide to how I work is:

  • choose fork on here to create your own copy of mvx
  • assuming you're on windows, then install http://gitextensions.googlecode.com/ selecting default options along the way
  • once installed, then choose Remotes>Putty>Generate Key to create a new SSH2 Key - save the private key somewhere and copy the public key to https://github.com/settings/ssh
  • after that... with perhaps just a little bit more fiddling through "Load Key" dialogs, then you should be able to:
    • clone your fork to a local git repository on your own hard drive
    • edit the code
    • commit changes in your fork back to you local git repository
    • push changes in your fork up to your github git repository

Given how git seems to be winning, it's a skill worth learning (IMO)

If not.... if that's too complicated, then you can also copy a small number of files using http://gist.github.com

Hope that helps

@danielcweber
Copy link
Contributor Author

Did it! It's in Library/WeakEventManager https://github.com/Blewzman/MvvmCross/tree/b4030a7fae01eba8bdd278d9ba59f60e463e260a/Library/WeakEventManager

Let me know what you think of it.

@slodge
Copy link
Contributor

slodge commented Sep 14, 2012

Welcome to git ;) If you ever need free private git repositories, then try bitbucket.org.


I think this is definitely an interesting idea... And the fact that MvvmLight uses a similar approach (in a slightly different scenario) encourages me.

Are you thinking that all INotifyPropertyChanged and INotifyCollectionChanged subscriptions should go through weak ref mechanisms? Or should just some of them?

I guess it would be possible to make this configurable per-use - but I have a feeling that this should go "all in" if it happens as that would make the architecture "cleaner" and "simpler"

I'm also wondering about whether to do both this and the null change - I can see cases where users would expect that disposing the binding without destroying the view - and in that case they would want the events to stop...

Always interesting!

@danielcweber
Copy link
Contributor Author

I really think too that this should go "all in" - mainly because it is not at all obvious where to use weak refs and where not. Best thing, the 'set to null' feature wouldn't even be necessary then, since the INotifyCollectionChanged subscription would eventually go away too.

I don't think disposing the binding without disposing the view is such a reasonable scenario (although possible I think). Given that we ideally design our views in declarative ways (xaml/axml), it seems rather strange to declare a view and the bindings in xaml and then change that programmatically by disposing the binding.

@slodge
Copy link
Contributor

slodge commented Sep 17, 2012

I'm still thinking about this....

My current thoughts are:

  • yes, we should absolutely use weakreferences in release mode - especially for those cases where leaks happen...
  • we should also do something in order to break the linking when the Dispose() occurs. I agree that this is only for edge cases. But it still feels important to me that we should try to do this as cleanly as we can while the problems in front of us, and not just wait for GC to tidy up the weakly referenced objects at its convenience.

As well as the "set to null" idea I've also wondered about adding some sort of disposable wrapper around INotifyCollectionChanged and INotifyPropertyChanged within in target binding code - but I'm really not sure about which is the lesser evil!

The good news is this is all about "edge cases" - so there's no huge time pressure on this (phew!)

Stuart

@danielcweber
Copy link
Contributor Author

The cases are not that "edgy". For example I found out that the BindableListView does not have the workaround you did for the BindableLinearLayout, and it wouldn't even be possible because it does not raise the OnChildViewRemoved event. So there's no chance of BindableListView to not leak as far as I can tell.

@slodge
Copy link
Contributor

slodge commented Sep 17, 2012

Thanks again

I had to go back and remind myself what you were talking about - it's been a while since that workaround :)

However, I don't think that the original problem effects the ListView.

I don't believe the ListView needs an OnChildViewRemoved as it doesn't maintain one view per list item - instead it virtualises the UI and maintains a reusable set of views (convertView's) which it uses for all the list items.

As you add and remove children from the list, then (unlike the linearlayout) the set of child views in the ListView should not grow too large - instead it should just be the right number to fill the screen.

For a "normal" activity inflated from "normal" XML then all of the child bindings should get disposed when the activity disappears (after the fix for BindingInflate above). And then when the list gets garbage collected so should that set of child views.

So as I currently understand it, the only open problem here is with the leak caused by the INotifyCollectionChanged subscription. This is the case where:

  • the app removes the listview,
  • but (for some reason) the app continues to keep a reference to the observable collection
  • and the leak means that the observable collection keeps a reference to the listview.

As far as I can see, this shouldn't occur for most apps where the observablecollection should get garbage collected at the same time as the ViewModel and the View... because most observablecollections will be owned by the ViewModel.

That's why I think it's "edgy". This will only occur where the observablecollection lifetime exists beyond the lifetime of the ViewModel, or where listviews (panels?) are dynamically created and removed within an activity without the activity itself ever being destroyed.

Please be assured that when I say "edgy" I don't mean that I won't fix it... I just mean that I don't think this leak occurs in typical, normal use - e.g. I don't think it occurs in any of the 6 samples.

At least, from what the debugger is telling me I think that's the case...

@slodge
Copy link
Contributor

slodge commented Sep 21, 2012

I think I'm reaching my conclusion on this point.

Here's what I'd like to propose....

  1. I'd like to actively prevent the leak (the one in Android: MvxBindableListItemView instances leaking #17 (comment)) by setting the value to null on dispose in MvxPropertyInfoTargetBinding:
        protected override void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                // if the target type is an object type, then we clear the value during the disposing of the binding
                // this is a fix for the possible memory leaks discussion started https://github.com/slodge/MvvmCross/issues/17#issuecomment-8527392
                if (!TargetType.IsValueType)
                {
                    SetValue(null);
                }
            }

            base.Dispose(isDisposing);
        }

I prefer to do this actively rather than waiting for WeakReferences as I personally feel this is the correct way to do it and I can't think of any situation where we can't Dispose the binding. I feel like we shouldn't be relying on WeakReferences to clear up my mistakes.

I've tested this on Droid and it seems to clear up the objects and didn't have any adverse effects on any of the samples. Will test on Touch "soon"

  1. Having said that... I do want to include a WeakReference event subscription manager into the vNext source tree - I intend to shift most of my dev effort to vNext now. In particular, I would like to use a WeakReference manager for:
  • view-viewModel binding event subscription (maybe this will only be used if a global flag is set)
  • messaging - I would like to add an MvvmLight type messenger to allow ViewModels to communicate more easily with each other.

Does this sit OK with you? Do you think I'm making a big mistake in including the SetToNull code? Is there some bug you are worried that I am causing.

Thanks again for your input - it really is valuable!

Stuart

@danielcweber
Copy link
Contributor Author

Hi Stuart,

I don't really know yet whether setting the property to null is always a good thing to do - the setter might throw an ArgumentNullException when null is an invalid value for the property, you might catch it but performance would decrease (which is not a big consideration now for me). I think it would be bad style to provoke and catch such potential exceptions as part of the normal control flow and there's no way to determine valid values for a property.

Worse, null might have some semantics for certain properties, and the setter will not be able to tell whether is has been passed null because of normal operation or disposal of the binding.

I did the set-to-null-BindingFactory workaround for a specific propertyName ("ItemsSource"), so I could control the effects to a certain extent. Making it a default, I really don't know.

I feel like we shouldn't be relying on WeakReferences to clear up my mistakes.

I don't think this is a mistake per se. We don't know anything about the Views or the ViewModels, we don't know about their internal workings, we don't know whether null is a valid value for a target property. But if you don't know anything, how can you tell whether a view will leak if you don't set it's xyz-property to null? The ItemsSource property is just a special case that we now know about.

(maybe this will only be used if a global flag is set)

This is a good idea - do you mean a compiler directive or something that can be set dynamically at runtime?

Would you say it's reasonable to switch to the vNext branch right now ?

Daniel

@slodge
Copy link
Contributor

slodge commented Sep 21, 2012

Thanks again, Daniel

I like the thinking around "we don't know whether null is a valid value for a target property"

We could add an attribute - something like:

[AttributeUsage(AttributeTargets.Property)]
public class SetToNullAfterBindingAttribute : Attribute
{
}

This could then be used as:

    [SetToNullAfterBinding]
     public IList ItemsSource
    {
        get { return Adapter.ItemsSource; }
        set { Adapter.ItemsSource = value; }
    }

and:

    protected override void Dispose(bool isDisposing)
    {
        if (isDisposing)
        {
            // if the target property should be set to NULL on dispose then we clear it here
            // this is a fix for the possible memory leaks discussion started https://github.com/slodge/MvvmCross/issues/17#issuecomment-8527392
            var shouldSetToNull = _targetPropertyInfo.GetCustomAttributes(typeof(SetToNullAfterBindingAttribute), true).Any();
            if (shouldSetToNull)
            {
                SetValue(null);
            }
        }

        base.Dispose(isDisposing);
    }

On vNext, I do need to check it against the latest MonoTouch code (6.0 just released) but overall yes I think it is usable and stable - the main problem for users is the required hacking to get the tooling working perfectly :/

The main code changes for apps are:

  • changes from Cirrious.MvvmCross.Android to Cirrious.MvvmCross.Droid - changed because I was finding too much code with global::Android naming clashes
  • changes from FirePropertyChanged to RaisePropertyChanged to make the code more mvvmlight friendly
  • changes if your ViewModels use non-PCL code - but in this case you can carry on using multiple non-PCL core projects if you want to.

There's no hurry for anyone to switch over - but I do very strongly think that the PCL approach is the future. Some people will no doubt hate the PCL approach - in which case I will keep vOld there for them to use - but as a Resharper user, it's heaven for me!


Will get lots done this weekend - back to work now :)

Stuart

@danielcweber
Copy link
Contributor Author

Attributes are good, but what about the cases where a user would want to set an attribute on a method in a closed source assembly ?

Maybe this should be set directly in XAML by the notion of a fallback value as in http://msdn.microsoft.com/en-us/library/system.windows.data.bindingbase.fallbackvalue.aspx

@slodge
Copy link
Contributor

slodge commented Sep 21, 2012

what about the cases where a user would want to set an attribute on a method in a closed source assembly

That's definitely an edge case ... but I guess they could always inherit from the control... or if absolutely necessary they could use a custom binding

The code does already have FallbackValues - they're stored and used in the MvxFullBinding class. They could be used, but they're kind of there for a different purpose - they're there for genuinely missing sources.

Thanks again

Back to work!

@danielcweber
Copy link
Contributor Author

they're there for genuinely missing sources.

So it all would come down to whether we can think of a disposed binding as some kind of missing source, wouldn't it ?

@slodge
Copy link
Contributor

slodge commented Sep 21, 2012

Not really - the arguments against null also stands for the arguments against FallbackValue - for objects there's no real fallback value available in xml or json - so default(T) gets used - which is null...


I could push this as far as an attribute which somehow indicated a method to call on unbinding - e.g.

    [MvxCallOnDisposeBinding('UnbindItemsSource')]
    public IList ItemsSource
    {
        get { return Adapter.ItemsSource; }
        set { Adapter.ItemsSource = value; }
    }

    public void UnbindItemsSource'()
    {
         // do special clear up here...
    }

or this info could even go into a new field in the binding JSON?!


But for now.... I'll test out the simpler attribute first - want to make sure it doesn't do anything nasty when e.g. ItemsSource and SelectedItem are both bound (e.g. does a SelectedItemChange event fire if I dispose ItemsSource before SelectedItem?)

It's good to be busy :) But I really need to get some unit tests on vNext!

Stuart

slodge added a commit that referenced this issue Sep 25, 2012
…listviews - this version is mostly tested... but there's potentially more still to do!
@slodge
Copy link
Contributor

slodge commented Sep 25, 2012

Pushed an attribute based fix for this... but now really want to do weakReferencedEvents too - especially as I understand more about UIKit on MonoTouch

@slodge
Copy link
Contributor

slodge commented Dec 21, 2012

I will close this.... when I implement some weak reference stuff (maybe as part of #61)

@slodge
Copy link
Contributor

slodge commented Feb 14, 2013

Was talking about this again yesterday.

WeakReferences will happen - but possibly only at the final layer of binding to the UI

@danielcweber
Copy link
Contributor Author

Great to hear.

I was removing some of the code I added to my fork of MvvmCross to get the leaks out, now hat we have SetToNullAfterBindingAttribute. I was able to restore all the original code except for two places in MvxBindingActivityView where the original code still leaks. I was able to stop the leakage by inserting

var viewGroup = view as ViewGroup;
if (viewGroup != null)
{
    for (int i = 0; i < viewGroup.ChildCount; i++)
    {
        var child = viewGroup.GetChildAt(i);
        this.ClearBindings(child);
    }
}

at the end of MvxBindingActivityView.ClearBindings(View view) and replacing the body of MvxBindingActivityView.ClearBoundViews() by

_boundViews.ForEach(this.ClearBindings);

This essentially does a recursive cleanup and kills the leaks. I tried but I can't see where the original, non-recursive cleanup fails.

@slodge
Copy link
Contributor

slodge commented Feb 22, 2013

Thanks for bearing with me....

I'm still seriously thinking about adding the weakeventmanager too.

ie. adding it as well as all the other code

It's grown on me over the last 8 months!

8 months?! Sorry - I'm slow sometimes!

Stuart

@slodge
Copy link
Contributor

slodge commented Feb 23, 2013

Hi Daniel

Sometimes it just takes time for me to get things....

So please do exactly as you have one here - post me suggestions and then leave me alone for a few months...

I'll get there eventually :)

The current v3 prototype code is https://github.com/slodge/MvvmCross/tree/vNextDialog/Cirrious/Cirrious.MvvmCross.Binding/WeakSubscription

It has some accompanying extension methods in https://github.com/slodge/MvvmCross/blob/vNextDialog/Cirrious/Cirrious.MvvmCross.Binding/ExtensionMethods/MvxWeakSubscriptionExtensionMethods.cs

And it's used like in https://github.com/slodge/MvvmCross/blob/vNextDialog/Cirrious/Cirrious.MvvmCross.Binding.Droid/Views/MvxAdapter.cs

It's loosely based on your weakreferencemanager code - thanks :)

It's very early code yet - will add some test harness and do some more testing tomorrow.

I will get there eventually

Thanks

Stuart

@slodge
Copy link
Contributor

slodge commented Mar 29, 2013

I'm going to be very brave and close this issue.

Daniel/Blewz - no idea who you are... but I'm very grateful for your patience and skill on exploring this area with me.

It feels like the Weak references in v3 are awesome. I'm sure we'll hit subtle bugs with them - but overall they make me very happy.

Thanks!

@slodge slodge closed this as completed Mar 29, 2013
martijn00 added a commit to martijn00/MvvmCross that referenced this issue Dec 8, 2016
Fixed crash when initial View is marked as ActivePanel
kjeremy pushed a commit to kjeremy/MvvmCross that referenced this issue Dec 11, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
t/bug Bug type
Development

No branches or pull requests

2 participants