MVVMCross Binding Crashes Android Application-Collection of common programming errors
Alexander MarekI have an Android App based on Xamarin and MvvmCross. In that application there is a view with an ExpandableListView that I created on my own. Now this list displays several items, which are bound to their DataContext using MvvmCross. However, as the views of individual ListItemViews differs so much, part of those ListItemViews is generated programmatically in the ExpandedListViewAdapter. This works like so:
public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent) { object child = GetRawChild(groupPosition, childPosition); if (child == null) { MvxBindingTrace.Trace(MvxTraceLevel.Error, "GetView called for group that seems to have no itemssource: it is null"); return null; } var view = (MvxListItemView)GetBindableView(convertView, child, ChildItemTemplateId); var placeholder = view.FindViewById(Resource.Id.placeholder); var questionVm = (QuestionViewModel)child; if(questionVm.ViewType == "TextBox") { placeholder.RemoveAllViews(); var text = new BindableEditText(context); text.InputType = InputType; text.SetRawInputType(InputType); placeholder.RemoveAllViews(); placeholder.AddView(text); var answer = questionVm.Children.First(); text.DataContext = answer; var binding = text.CreateInlineBindingTarget(); text.Bind(binding, et => et.Text, vm => vm.Model.Value, (string)null, null, null, MvxBindingMode.TwoWay); } else if(questionVm.ViewType == "Spinner") { placeholder.RemoveAllViews(); MvxSpinner spinner = new MvxSpinner(context, null); spinner.ItemsSource = questionVm.Children; spinner.ItemSelected += (sender, args) => { for (int i = 0; i < questionVm.Children.Count; i++) { var answer = (IAnswerViewModel)questionVm.Children[i]; if (i == spinner.SelectedItemPosition) answer.IsSelected = true; else answer.IsSelected = false; } }; spinner.Bind(bindings, ctrl => ctrl.ItemsSource, vm => vm.Children, (string)null, null, null, MvxBindingMode.OneWay); var chosenAnswer = questionVm.Children.Cast().FirstOrDefault(@a => @a.IsSelected == true); if (chosenAnswer != null) spinner.SetSelection(questionVm.Children.Cast().ToList().IndexOf(chosenAnswer)); placeholder.AddView(spinner); }
… and “BindableEditText” looks like the following: using System; using Android.Content; using Android.Runtime; using Android.Views; using Android.Widget; using Android.Util; using Cirrious.MvvmCross.Binding.Droid.BindingContext; using Cirrious.MvvmCross.Binding.BindingContext;
namespace iCL.Filler.Droid.Controls { public class BindableEditText : EditText, IMvxBindingContextOwner { private readonly IMvxAndroidBindingContext _bindingContext; public BindableEditText(Context context) : base(context) { _bindingContext = new MvxAndroidBindingContext(context, null); } public BindableEditText(Context context, IAttributeSet attributes) : base(context, attributes) { _bindingContext = new MvxAndroidBindingContext(context, null); } public BindableEditText(Context context, IAttributeSet attributes, int defStyle) : base(context, attributes, defStyle) { _bindingContext = new MvxAndroidBindingContext(context, null); } public BindableEditText(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { } protected IMvxAndroidBindingContext AndroidBindingContext { get { return _bindingContext; } } public IMvxBindingContext BindingContext { get { return _bindingContext; } set { throw new NotImplementedException("BindingContext is readonly in the radio button"); } } protected override void Dispose(bool disposing) { if (disposing) { this.BindingContext.ClearAllBindings(); } base.Dispose(disposing); } public override void SetText(Java.Lang.ICharSequence text, TextView.BufferType type) { try { base.SetText(text, type); } catch (Exception ex) { } } protected View Content { get; set; } public object DataContext { get { return _bindingContext.DataContext; } set { _bindingContext.DataContext = value; } } } }
So my problem is, that once in a while, when I am scrolling and clicking through my listview, I get a runtime error like the following, and my app “crashes” which means in effect that it navigates back to the previous screen.
10-29 14:04:37.140 D/dalvikvm( 5989): GC_EXPLICIT freed 751K, 11% free 11369K/12679K, paused 0ms+1ms 10-29 14:04:39.970 D/dalvikvm( 5989): GC_FOR_ALLOC freed 715K, 16% free 10692K/12679K, paused 5ms 10-29 14:04:41.831 E/mono-rt ( 5989): Stacktrace: 10-29 14:04:41.831 E/mono-rt ( 5989): 10-29 14:04:41.831 E/mono-rt ( 5989): at 10-29 14:04:41.831 E/mono-rt ( 5989): at (wrapper managed-to-native) object.wrapper_native_0xb71f1820 (intptr,intptr,intptr,intptr,Android.Runtime.JValue[]) 10-29 14:04:41.831 E/mono-rt ( 5989): at Android.Runtime.JNIEnv.CallNonvirtualVoidMethod (intptr,intptr,intptr,Android.Runtime.JValue[]) [0x00000] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.8.2-branch/a25a31d0/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:612 10-29 14:04:41.831 E/mono-rt ( 5989): at Android.Widget.CompoundButton.set_Checked (bool) [0x00070] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.8.2-branch/a25a31d0/source/monodroid/src/Mono.Android/platforms/android-14/src/generated/Android.Widget.CompoundButton.cs:255 10-29 14:04:41.831 E/mono-rt ( 5989): at (wrapper runtime-invoke) .runtime_invoke_void__this___byte (object,intptr,intptr,intptr) 10-29 14:04:41.831 E/mono-rt ( 5989): at 10-29 14:04:41.831 E/mono-rt ( 5989): at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) 10-29 14:04:41.831 E/mono-rt ( 5989): at System.Reflection.MonoMethod.Invoke (object,System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo) 10-29 14:04:41.831 E/mono-rt ( 5989): at System.Reflection.MethodBase.Invoke (object,object[]) 10-29 14:04:41.831 E/mono-rt ( 5989): at Cirrious.MvvmCross.Binding.Bindings.Target.MvxPropertyInfoTargetBinding.SetValueImpl (object,object) 10-29 14:04:41.831 E/mono-rt ( 5989): at Cirrious.MvvmCross.Binding.Bindings.Target.MvxConvertingTargetBinding.SetValue (object) The program 'Mono' has exited with code 0 (0x0).
I do not know what the problem could be…. Could it be, that some Java object is already finalized, and my bindings try to call it?
Alexander MarekThanks stuart, but onestly I only pasted some of the code I wrote as my listadapter has become quite complex yet. So actually I already was recycling the controls that I created in code. However it seems I found the solution. I watched my app crashes lately and they all seemed to be because some binding calls the setter of an android widget (EditText, Spinner, Checkbox, etc.) which resulted in a native error. So I placed trace messages into the “Dispose” and “JavaFinalize” methods and as it turns out, the error always happened after some “JavaFinalize” calls. So what I did was to add the following code to all the controls, that I implemented as a “Bindable”something. (like the BindableEditText in the snippet above)
protected override void JavaFinalize() { if (this.BindingContext != null) this.BindingContext.ClearAllBindings(); base.JavaFinalize(); }
So that totally does the job!
WARNING: I also had to add this to the “MvxListItemView”. So maybe it should be added to the mvvmcross sources as well?
StuartIt’s a bit hard to follow the code snippet provided, but I would guess that the problem you are seeing is due to the fact that you are recycling the
cell
and that each time you recycle it you are:- creating a new cell child view,
- creating a new binding,
- clearing the old child view
- but not removing the old binding.
One way to solve this might be to use the
WithClearBindingKey
fluent method to allow you to clear these bindings.For example, if a binding is created as:
var set = this.CreateBindingSet(); set.Bind(text).To(vm => vm.TextValue).WithClearBindingKey("MyDynamicBindings"); set.Apply();
then just the bindings created with this tag can be cleared using:
this.ClearBindings("MyDynamicBindings");