Forum Xamarin.Forms

GC.Collect() strange behavior after Navigation.PopAsync() Or hitting Back Button

Using Xlabs, I've been able to use Navigation.PushAsync() and Navigation.PopAsync() without having any issue. But recently I added a Radio Button custom control found online (with a few modifications) and started using it. The control is fine and work as expected (both on Android and iOS) .

Heres the parent control (containing all the radio buttons), before I explained the issue:

public class BindableRadioGroup : StackLayout {
    public List<CustomRadioButton> rads;

    public BindableRadioGroup() {
        rads = new List<CustomRadioButton>();
        ItemMargin = "0,0,0,0";
    }

    public static readonly BindableProperty ItemsSourceProperty =
        BindableProperty.Create("ItemsSource", typeof(IEnumerable),typeof(BindableRadioGroup), default(IEnumerable), BindingMode.TwoWay, propertyChanged: OnItemsSourceChanged);

    public static BindableProperty SelectedIndexProperty =
        BindableProperty.Create("SelectedIndex", typeof(int), typeof (BindableRadioGroup), -1, BindingMode.TwoWay, propertyChanged: OnSelectedIndexChanged);

    public IEnumerable ItemsSource {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }
    public int SelectedIndex {
        get { return (int)GetValue(SelectedIndexProperty); }
        set { SetValue(SelectedIndexProperty, value); }
    }        
    public static string ItemMargin { get; set; }

    public event EventHandler<int> CheckedChanged;

    static void OnItemsSourceChanged(BindableObject bindable, object oldvalue, object newvalue) {

        if (Equals(newvalue, null) && Equals(oldvalue, null)) {
            return;
        }
        if (newvalue != null) {
            var radButtons = bindable as BindableRadioGroup;

            radButtons.rads.Clear();
            radButtons.Children.Clear();

            int radIndex = 0;
            string[] delims = { "," };
            string[] tokens = ItemMargin.Split(delims, StringSplitOptions.RemoveEmptyEntries);
            var left = Convert.ToInt32(tokens[0]);
            var top = Convert.ToInt32(tokens[1]);
            var right = Convert.ToInt32(tokens[2]);
            var bottom = Convert.ToInt32(tokens[3]);
            foreach (var item in (IEnumerable)newvalue) {
                var rad = new CustomRadioButton();
                rad.Margin = new Thickness(left, top, right, bottom);
                rad.Text = item.ToString();
                rad.Id = radIndex;
                rad.CheckedChanged += radButtons.OnCheckedChanged;
                radButtons.rads.Add(rad);
                radButtons.Children.Add(rad);
                radIndex++;
            }
        } 
    }
    private void OnCheckedChanged(object sender, EventArgs<bool> e) {
        if (e.Value == false) {
            if (((CustomRadioButton)sender).Id == SelectedIndex) {
                ((CustomRadioButton)sender).Checked = true;
            }                
            return;
        }
        var selectedRad = sender as CustomRadioButton;
        foreach (var rad in rads) {
            if (!selectedRad.Id.Equals(rad.Id)) {
                rad.Checked = false;
            } else {
                if (CheckedChanged != null)
                    CheckedChanged.Invoke(sender, rad.Id);
            }
        }
        SelectedIndex = selectedRad.Id;
    }

    private static void OnSelectedIndexChanged(BindableObject bindable, object oldvalue, object newvalue) {
        if ((int)newvalue == -1) return;
        var bindableRadioGroup = bindable as BindableRadioGroup;
        foreach (var rad in bindableRadioGroup.rads) {
            if (rad.Id == bindableRadioGroup.SelectedIndex) {
                rad.Checked = true;
            }
        }
    }
}

The issue (only seen on iOS) is when I use Navigation.PopAsync() or Back button to pop the current page (containing the custom control), all the children buttons are removed from the parent Stack Layout, but it seems like (to my understanding) the parent Stack Layout itself is SOMETIMES not collected by GC. This leaves the current page blank without being popped; OR the app goes to the previous page successfully, and then on that page if I hit Back button or any button calling PopAsync(), then that page goes blank without being popped.

Eventually, I added the line GC.Collect() after Navigation.PopAsync() and inside OnBackButtonPressed(); then the issue no longer happens, even though I'm not sure why GC wasn't working properly before that.
After this, I wasn't able to reproduce the issue at all, so I guess I'm posting it here to see if anyone could see there's something unusual or not recommended in my code that might have triggered the strange event.

Posts

  • EmmanuelVazquezEmmanuelVazquez USMember ✭✭

    I am seeing this with my iOS app too and I use mostly out of box controls. Do you have a small repo to submit this issue to bugzilla? Mine is fairly large.

Sign In or Register to comment.