Search code examples
androidxamarindata-bindingbindmvvmcross

MvxBind CheckedChange SwitchCompat


I'm having troubles withing binding my Android.Support.V7.Widget.SwitchCompat to my viewmodel (Mvvmcross is the framework i'm using) I did exactly the same as with the click bind on another object which works perfectly fine.

The error i'm getting when viewing the view is as following:

12-21 10:32:06.459 I/MvxBind (22969):  42,82 Failed to create target binding for binding CheckedChange for OnCheckedChanged
[0:] MvxBind:Warning: 42,82 Failed to create target binding for binding CheckedChange for OnCheckedChanged

Multiple times for the amount of switches i have.

They said it might have to do something with the linker not including stuff because of reflection magic.

This way they said you have to create a file "LinkerPleaseInclude" to keep a reference to your switchcompat. I did so as following, but still the error persists.

LinkerPleaseInclude

class LinkerPleaseInclude
{
    public void Include(TextView text)
    {
        text.AfterTextChanged += (sender, args) => text.Text = "" + text.Text;
        text.Hint = "" + text.Hint;
    }

    public void Include(CompoundButton cb)
    {
        cb.CheckedChange += (sender, args) => cb.Checked = !cb.Checked;
        cb.Hint = "" + cb.Hint;
    }
    public void Include(SwitchCompat cb)
    {
        cb.CheckedChange += (sender, args) => cb.Checked = !cb.Checked;
        cb.Hint = "" + cb.Hint;
    }
    public void Include(ICommand command)
    {
        command.CanExecuteChanged += (s, e) => { if (command.CanExecute(null)) command.Execute(null); };
    }
    public void Include(CheckBox checkBox)
    {
        checkBox.CheckedChange += (sender, args) => checkBox.Checked = !checkBox.Checked;
    }
}

My ViewLayout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="@dimen/md_list_single_line_item_height"
    android:gravity="center_vertical"
    android:paddingLeft="@dimen/md_list_item_horizontal_edges_padding"
    android:paddingRight="@dimen/md_list_item_horizontal_edges_padding">
  <android.support.v7.widget.SwitchCompat
    android:id="@+id/mySwitch"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_centerVertical="true"
    local:MvxBind="Checked IsActive; Click OnSwitchClick; CheckedChange OnCheckedChanged" />
  <TextView
    android:id="@+id/Name"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:textColor="@color/md_text_dark_primary_87"
    android:textSize="@dimen/md_list_item_primary_text"
    local:MvxBind="Text Name"/>
  <TextView
    android:id="@+id/Kind"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"
    android:textColor="@color/md_text_dark_secondary_54"
    android:textSize="@dimen/md_list_item_secondary_text"
    android:layout_below="@+id/Name"
    local:MvxBind="Text Kind"/>
</RelativeLayout>

this layout is a child of another layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:local="http://schemas.android.com/apk/res-auto"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="@dimen/md_list_two_line_item_height"
  android:paddingTop="?android:attr/actionBarSize"
  android:fitsSystemWindows="true">
  <MvxClickableLinearLayout
      android:id="@+id/animalSelectionsList"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical"
      android:divider="@drawable/divider_horizontal"
      android:showDividers="middle"
      local:MvxBind="ItemsSource SelectionsList"
      local:MvxItemTemplate="@layout/listitem_animal_selections" />
</LinearLayout>

Solution

  • CheckedChange is an event, hence it is not a public property. You cannot bind events in MvvmCross directly, unless you create your own Target Binding that handles this and exposes a Command.

    This could look something like:

    public class MvxCompoundButtonCheckedChangeBinding : MvxAndroidTargetBinding
    {
        private ICommand _command;
        private IDisposable _checkedChangeSubscription;
        private IDisposable _canExecuteSubscription;
        private readonly EventHandler<EventArgs> _canExecuteEventHandler;
    
        protected CompoundButton View => (CompoundButton)Target;
    
        public MvxCompoundButtonCheckedChangeBinding(CompoundButton view)
            : base(view)
        {
            _canExecuteEventHandler = OnCanExecuteChanged;
            _checkedChangeSubscription = view.WeakSubscribe<CompoundButton, CompoundButton.CheckedChangeEventArgs>(nameof(view.CheckedChange), ViewOnCheckedChangeClick);
        }
    
        private void ViewOnCheckedChangeClick(object sender, CompoundButton.CheckedChangeEventArgs args)
        {
            if (_command == null)
                return;
    
            if (!_command.CanExecute(null))
                return;
    
            _command.Execute(view.Checked);
        }
    
        protected override void SetValueImpl(object target, object value)
        {
            _canExecuteSubscription?.Dispose();
            _canExecuteSubscription = null;
    
            _command = value as ICommand;
            if (_command != null)
            {
                _canExecuteSubscription = _command.WeakSubscribe(_canExecuteEventHandler);
            }
            RefreshEnabledState();
        }
    
        private void RefreshEnabledState()
        {
            var view = View;
            if (view == null)
                return;
    
            var shouldBeEnabled = false;
            if (_command != null)
            {
                shouldBeEnabled = _command.CanExecute(null);
            }
            view.Enabled = shouldBeEnabled;
        }
    
        private void OnCanExecuteChanged(object sender, EventArgs e)
        {
            RefreshEnabledState();
        }
    
        public override MvxBindingMode DefaultMode => MvxBindingMode.OneWay;
    
        public override Type TargetType => typeof(ICommand);
    
        protected override void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                _checkedChangeSubscription?.Dispose();
                _checkedChangeSubscription = null;
    
                _canExecuteSubscription?.Dispose();
                _canExecuteSubscription = null;
            }
            base.Dispose(isDisposing);
        }
    }
    

    Then you need to register it in your Setup.cs in a FillTargetFactories override:

    registry.RegisterCustomBindingFactory<View>("MyCheckedChange", 
        view => new MvxCompoundButtonCheckedChangeBinding(view));
    

    Then you can bind MyCheckedChange to your command.