Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
[iOS] Unsubscribe CellPropertyChanged when SwitchCellRenderer is dis…
Browse files Browse the repository at this point in the history
…posed (#3518)

* [Controls] Add repo of issue #3408

* [iOS] Unsubscribe CellPropertyChanged when cell is disposed

* [Controls] Add issue to project

* [iOS] Introduce CellPropertyChange to CellTableViewCell to used by all other cell renderers

* [Controls] Simplify Issue

* [iOS] Remove PropertyChanged that was not used

* [Controls] add check for null binding on template, add notes about issues

* [Controls] added reference to Android crash

* [iOS]Use existing PropertyChanged on ViewCelRenderer

* [iOS] Keep the existing override so we don't break users

* Update TextCellRenderer.cs

* update submodule
  • Loading branch information
rmarinho authored and Jason Smith committed Sep 5, 2018
1 parent 9ad4cb8 commit ec76761
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 28 deletions.
2 changes: 1 addition & 1 deletion Xamarin.Forms.Build
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
using System;
using System.Collections.Generic;
using System.Linq;
using static Xamarin.Forms.Controls.Issues.Issue3408;

#if UITEST
using Xamarin.UITest;
using NUnit.Framework;
#endif

namespace Xamarin.Forms.Controls.Issues
{
// This may crash for you on Android if you click too many buttons
// https://github.com/xamarin/Xamarin.Forms/issues/3603
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 3408, "System.ObjectDisposedException: from SwitchCellRenderer when changing ItemSource", PlatformAffected.iOS)]
public class Issue3408 : TestContentPage
{
public static List<Recommendation> GetRecommendations(object e)
{
switch (e)
{
case List<RecommendationsViewModel> pc: return pc.First().Recommendations;
case List<RecommendationsViewModel2> pc: return pc.First().Recommendations;
default: return null;
}
}
protected override void Init()
{

var grd = new Grid();

var aacountListView = new ListView();
aacountListView.HasUnevenRows = true;
aacountListView.ItemTemplate = new AccountDetailsDataTemplateSelector();
aacountListView.BindingContext = new List<RecommendationsViewModel> { new RecommendationsViewModel() };

aacountListView.SetBinding(ListView.ItemsSourceProperty, ".");
var btn = new Button
{
Text = "Change Source",
AutomationId = "btn1",
Command = new Command(() =>
{
aacountListView.BindingContext = new List<RecommendationsViewModel2> { new RecommendationsViewModel2() };
})
};
var btn2 = new Button
{
Text = "Change Property",
AutomationId = "btn2",
Command = new Command(() =>
{

foreach (var item in GetRecommendations(aacountListView.BindingContext))
{
item.Name = "New Item Name";
item.IsBusy = !item.IsBusy;
}

})
};
grd.Children.Add(aacountListView);
Grid.SetRow(aacountListView, 0);
grd.Children.Add(btn);
Grid.SetRow(btn, 1);
grd.Children.Add(btn2);
Grid.SetRow(btn2, 2);
Content = grd;
}

#if UITEST
[Test]
public void Issue3408Test ()
{
RunningApp.WaitForElement (q => q.Marked ("btn1"));
RunningApp.WaitForElement (q => q.Marked ("Click to Change"));
RunningApp.Tap(q => q.Marked("btn1"));
RunningApp.WaitForElement(q => q.Marked("This should have changed"));
RunningApp.Tap(q => q.Marked("btn2"));
RunningApp.WaitForElement(q => q.Marked("New Item Name"));
}
#endif

[Preserve(AllMembers = true)]
public class RecommendationsBaseViewModel : ViewModelBase
{
public string AccountName => $"";
public List<Recommendation> Recommendations { get; set; }
}

[Preserve(AllMembers = true)]
public class RecommendationsViewModel : RecommendationsBaseViewModel
{
public string AccountName => $"Recommendations";

public RecommendationsViewModel()
{
Recommendations = new List<Recommendation>()
{
new Recommendation(){ Name = "Click to Change"} ,
new Recommendation(){ Name = "Recommendations"} ,
new Recommendation(){ Name = "Recommendations"} ,
};
}
}

[Preserve(AllMembers = true)]
public class RecommendationsViewModel2 : RecommendationsBaseViewModel
{
public string AccountName => $"Recommendations 2";
public RecommendationsViewModel2()
{
Recommendations = new List<Recommendation>()
{
new Recommendation(){ Name = "This should have changed"} ,
new Recommendation(){ Name = "Recommendations 2"} ,
new Recommendation(){ Name = "Recommendations 2", IsBusy = true } ,
};
}
}

[Preserve(AllMembers = true)]
public class Recommendation : ViewModelBase
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
OnPropertyChanged();
}
}
}

}

[Preserve(AllMembers = true)]
public class RecommendationsView : ContentView
{
public RecommendationsView()
{
Grid grd = new Grid();
var lst = new ListView
{
ItemTemplate = new DataTemplate(() =>
{
var swittch = new SwitchCell();
swittch.SetBinding(SwitchCell.TextProperty, new Binding("Name"));
swittch.SetBinding(SwitchCell.OnProperty, new Binding("IsBusy"));
return swittch;
})

};

lst.SetBinding(ListView.ItemsSourceProperty, new Binding("Recommendations"));
grd.Children.Add(lst);
Content = grd;
}

// This work around exists because of this issue
// https://github.com/xamarin/Xamarin.Forms/issues/3602
object context = null;
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext == null)
Device.BeginInvokeOnMainThread(() => BindingContext = context);
else
context = BindingContext;
}
}

[Preserve(AllMembers = true)]
public class AccountDetailsDataTemplateSelector : DataTemplateSelector
{
public Lazy<DataTemplate> RecommendationsViewDataTemplate { get; }
public Lazy<ViewCell> RecommendationsView { get; }

public Lazy<DataTemplate> RecommendationsViewDataTemplate2 { get; }
public Lazy<ViewCell> RecommendationsView2 { get; }

public AccountDetailsDataTemplateSelector()
{
RecommendationsView = new Lazy<ViewCell>(() => new ViewCell() { View = new RecommendationsView() });
RecommendationsViewDataTemplate = new Lazy<DataTemplate>(() => new DataTemplate(() => RecommendationsView.Value));


RecommendationsView2 = new Lazy<ViewCell>(() => new ViewCell() { View = new RecommendationsView() });
RecommendationsViewDataTemplate2 = new Lazy<DataTemplate>(() => new DataTemplate(() => RecommendationsView2.Value));
}

protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (item == null)
{
return null;
}

if (item is RecommendationsViewModel)
{
RecommendationsView.Value.BindingContext = item;
return RecommendationsViewDataTemplate.Value;
}

if (item is RecommendationsViewModel2)
{
RecommendationsView2.Value.BindingContext = item;
return RecommendationsViewDataTemplate2.Value;
}

throw new ArgumentException("Invalid ViewModel Type");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Issue2728.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1667.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3012.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3408.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3413.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3525.cs" />
</ItemGroup>
Expand Down
23 changes: 15 additions & 8 deletions Xamarin.Forms.Platform.iOS/Cells/CellTableViewCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ namespace Xamarin.Forms.Platform.iOS
public class CellTableViewCell : UITableViewCell, INativeElementView
{
Cell _cell;

public Action<object, PropertyChangedEventArgs> PropertyChanged;

bool _disposed;

public CellTableViewCell(UITableViewCellStyle style, string key) : base(style, key)
Expand All @@ -19,27 +21,27 @@ public Cell Cell
get { return _cell; }
set
{
if (this._cell == value)
if (_cell == value)
return;

if (_cell != null)
{
_cell.PropertyChanged -= HandlePropertyChanged;
Device.BeginInvokeOnMainThread(_cell.SendDisappearing);

this._cell = value;
}
_cell = value;

if (_cell != null)
{
_cell.PropertyChanged += HandlePropertyChanged;
Device.BeginInvokeOnMainThread(_cell.SendAppearing);
}
}
}

public Element Element => Cell;

public void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
public void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) => PropertyChanged?.Invoke(sender, e);

internal static UITableViewCell GetNativeCell(UITableView tableView, Cell cell, bool recycleCells = false, string templateId = "")
{
Expand Down Expand Up @@ -102,6 +104,11 @@ protected override void Dispose(bool disposing)
if (disposing)
{
PropertyChanged = null;

if (_cell != null)
{
_cell.PropertyChanged -= HandlePropertyChanged;
}
_cell = null;
}

Expand Down
6 changes: 3 additions & 3 deletions Xamarin.Forms.Platform.iOS/Cells/EntryCellRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell,
tvc = new EntryCellTableViewCell(item.GetType().FullName);
else
{
tvc.Cell.PropertyChanged -= OnCellPropertyChanged;
tvc.PropertyChanged -= HandlePropertyChanged;
tvc.TextFieldTextChanged -= OnTextFieldTextChanged;
tvc.KeyboardDoneButtonPressed -= OnKeyBoardDoneButtonPressed;
}

SetRealCell(item, tvc);

tvc.Cell = item;
tvc.Cell.PropertyChanged += OnCellPropertyChanged;
tvc.PropertyChanged += HandlePropertyChanged;
tvc.TextFieldTextChanged += OnTextFieldTextChanged;
tvc.KeyboardDoneButtonPressed += OnKeyBoardDoneButtonPressed;

Expand All @@ -44,7 +44,7 @@ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell,
return tvc;
}

static void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
static void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
var entryCell = (EntryCell)sender;
var realCell = (EntryCellTableViewCell)GetRealCell(entryCell);
Expand Down
8 changes: 4 additions & 4 deletions Xamarin.Forms.Platform.iOS/Cells/ImageCellRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell,
return result;
}

protected override void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
protected override void HandleCellPropertyChanged(object sender, PropertyChangedEventArgs args)
{
var tvc = (CellTableViewCell)sender;
var imageCell = (ImageCell)tvc.Cell;
var imageCell = (ImageCell)sender;
var tvc = (CellTableViewCell)GetRealCell(imageCell);

base.HandlePropertyChanged(sender, args);
base.HandleCellPropertyChanged(sender, args);

if (args.PropertyName == ImageCell.ImageSourceProperty.PropertyName)
SetImage(imageCell, tvc);
Expand Down
6 changes: 3 additions & 3 deletions Xamarin.Forms.Platform.iOS/Cells/SwitchCellRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell,
else
{
uiSwitch = tvc.AccessoryView as UISwitch;
tvc.Cell.PropertyChanged -= OnCellPropertyChanged;
tvc.PropertyChanged -= HandlePropertyChanged;
}

SetRealCell(item, tvc);
Expand All @@ -33,7 +33,7 @@ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell,
var boolCell = (SwitchCell)item;

tvc.Cell = item;
tvc.Cell.PropertyChanged += OnCellPropertyChanged;
tvc.PropertyChanged += HandlePropertyChanged;
tvc.AccessoryView = uiSwitch;
tvc.TextLabel.Text = boolCell.Text;

Expand All @@ -48,7 +48,7 @@ public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell,
return tvc;
}

void OnCellPropertyChanged(object sender, PropertyChangedEventArgs e)
void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
var boolCell = (SwitchCell)sender;
var realCell = (CellTableViewCell)GetRealCell(boolCell);
Expand Down
Loading

0 comments on commit ec76761

Please sign in to comment.