I'm using MvvmCross with MonoDroid.
In a timer in the viewmodel, every minute I call RaisePropertyChanged("MinutesRemaining")
- MinutesRemaining
being an integer specifying the duration in minutes until the current entry ends (and yes, this is called on the UI thread!).
MinutesRemaining
is bound to a TextView
using MvvmCross.
Until the 4.10.1
update from Xamarin, the app would just crash completely with no error message printed to the trace - it's now breaking correctly when debugging and giving the below error when invoking the PropertyChanged
event:
MvxBind:Error:281.24 Problem seen during binding execution for binding Text for MinutesRemaining - problem ArgumentException: 'jobject' must not be IntPtr.Zero.
Parameter name: jobject
at Android.Runtime.JNIEnv.CallVoidMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue[] parms) [0x00010] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.10.1-branch/9d03ce3e/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:499
at Android.Widget.TextView.set_TextFormatted (ICharSequence value) [0x00034] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.10.1-branch/9d03ce3e/source/monodroid/src/Mono.Android/platforms/android-14/src/generated/Android.Widget.TextView.cs:1814
at Android.Widget.TextView.set_Text (System.String value) [0x00013] in /Users/builder/data/lanes/monodroid-mlion-monodroid-4.10.1-branch/9d03ce3e/source/monodroid/src/Mono.Android/platforms/android-14/src/generated/Android.Widget.TextView.cs:1823
at Cirrious.MvvmCross.Binding.Droid.Target.MvxTextViewTextTargetBinding.SetValueImpl (System.Object target, System.Object toSet) [0x00000] in <filename unknown>:0
at Cirrious.MvvmCross.Binding.Bindings.Target.MvxConvertingTargetBinding.SetValue (System.Object value) [0x00000] in <filename unknown>:0
at Cirrious.MvvmCross.Binding.Bindings.MvxFullBinding.UpdateTargetFromSource (System.Object value) [0x00000] in <filename unknown>:0
It binds correctly the first time - it's only on subsequent RaisePropertyChanged
calls that this occurs. The same code also works on Windows 8 and Windows Phone.
Using JavaFinalise in the adapter used for the scenario above fixed the problem (found here: MVVMCross Binding Crashes Android Application). The problem I have now is the same result, but where the first view in an adapter is bound to a property in the parent view model (not the item).
Code used to bind is as follows:
public class SubjectFilterAdapter : MvxAdapter {
private EntityListFragment<TEntity, TViewModel> _owner;
public SubjectFilterAdapter(Context context, EntityListFragment<TEntity, TViewModel> owner) : base(context, (IMvxAndroidBindingContext)owner.BindingContext) {
_owner = owner;
}
protected override View GetBindableView(View convertView, object dataContext, int templateId) {
var view = base.GetBindableView(convertView, dataContext, templateId);
if (templateId == ItemTemplateId && GetPosition(dataContext) == 0) {
var set = _owner.CreateBindingSet<EntityListFragment<TEntity, TViewModel>, TViewModel>();
set.Bind(view.FindViewById<TextView>(Resource.Id.SelectedScheduleText))
.To(x => x.SelectedScheduleText).WithClearBindingKey("SelectedScheduleTextFilterBinding");
set.Apply();
}
return view;
}
protected override void JavaFinalize() {
if (this.BindingContext != null)
this.BindingContext.ClearAllBindings();
base.JavaFinalize();
}
}
It works correctly to begin with (for the first couple of changes) but after that the above exception is thrown. Using MvvmCross 3.0.14-beta3
.
Thanks!
By mixing listitem/cell binding contexts with the parent context you're entering into quite an advanced area.
To help try to explain/debug what you is going on, you need to understand a little about all of the the parent lifecycle, the listitem/cell lifecycle and the lifecycles of the corresponding MvvmCross binding contexts.
At the parent lifecycle level, this is generally an Android Activity
or Fragment
. For the sake of simplicity I'll just use Activity
for the rest of this answer.
This Activity
has a couple of key lifecycle events
OnCreate
is called once only when the Activity
is first launchedOnDestroy
is called once only when the Activity
will not be shown again.MvvmCross intercepts these events and:
OnCreate
, it sets a ViewModel
as the Activity
's DataContext
. User code - normally Xml code inflated within SetContentView
- then creates bindings. These bindings are stored within the BindingContext
of the Activity
OnDestroy
, MvvmCross destroys all the bindings within that BindingContext
In the user interface we're interested in here, the Activity
owns a ListView
, and that ListView
has an Adapter
set for it. Within this scenario, the DataContext
for the ListView
and its Adapter
is the same as it's parent.
Within the lifetime of the ListView
the list may need to show lots of items. The items shown at any time may change - both because of user touch actions and because of view model changes. To display these items, the ListView
asks the Adapter
for View
s. For each item it shows the Adapter
supplies a View
, and these View
s may be reused (using the convertView
parameter). However, sometimes, these View
s are also not reused - and in this situation it is sometimes possible for the View object to live on in C# even after the Java/Dalvik View
has been removed and Java finalized.
MvvmCross intercepts the GetView
calls within its MvxAdapter
. For each call it returns not just a View
but moreover and MvxListItemView
. This is a View
with an added BindingContext
- and this allows MvvmCross users to bind each MvxListItemView
to its list item DataContext
.
When a MvxListItemView
is reused, it's simple for MvvmCross to simply change its DataContext
.
When a MvxListItemView
is not reused - when it is removed from the UI and then JavaFinalize
d - MvvmCross intercepts the OnDetachedFromWindow
event and it uses this to switch the DataContext
to null
. It does this on OnDetachedFromWindow
rather than JavaFinalize
as the Window call is guaranteed to be made on the UI thread and as it feels (to me) like a cleaner place to do this.
Note that some of this behaviour has subtly changed in recent releases - but this description above is correct for v3.0.14
With that background in place, what it appears you are currently trying to do is to create a binding for the contents of a ListItemView
within the BindingContext
of the Activity
.
This means that the binding doesn't really have a good understanding of the lifecycle of the ListItemView
- so the binding can say alive even after the ListItemView
has been removed from the screen and (maybe) finalized.
To resolve this issue...
JavaFinalize
in that answer is 'a bit naughty' in that it clears the parent Activity
s BindingContext during the Finalize
of the Adapter
. This is probably OK to do but it shouldn't really be necessary - the Activity
's own OnDestroy
should handle that.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With