Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'jobject' must not be IntPtr.Zero when setting textview's text in MonoDroid

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.

Update

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!

like image 860
Jamie Avatar asked Nov 14 '13 09:11

Jamie


1 Answers

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 launched
  • OnDestroy is called once only when the Activity will not be shown again.

MvvmCross intercepts these events and:

  • within 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
  • within 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 Views. For each item it shows the Adapter supplies a View, and these Views may be reused (using the convertView parameter). However, sometimes, these Views 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 JavaFinalized - 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...

  • I think the simplest route is to change the DataContext for your listitem. If your ListItemView binding is a simple normal binding - if it contains the MinutesRemaining property - then you shouldn't hit these lifecycle errors.
  • You can try the advanced binding found in @Jamie's answer (https://stackoverflow.com/a/20031690/373321) - however, I think this answer doesn't still quite get it right - as I think:
    • it doesn't correctly handle the case where the listitemview is removed/finalised - if the first list item is scrolled off-screen under the current code then I believe you can still see problems. To use Adapter-based code, then I think the Adapter needs to somehow get a callback from the listitemview in the case that that view is removed from the UI or finalised.
    • the JavaFinalize in that answer is 'a bit naughty' in that it clears the parent Activitys 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.
like image 68
Stuart Avatar answered Nov 15 '22 12:11

Stuart