Solving the “Event not found” issue in Xamarin MVVM Light binding and commanding

When using the MVVM Light binding framework (or other such frameworks, for that matter), you may encounter an issue where the debug version of your application works just fine, but the release version throws the following exception:

System.ArgumentException
Event not found: Click
(or any other event name)
Parameter name: eventName

So what’s happening here?

First we need to understand how bindings are created. The key to establishing a relationship between two properties is to listen to the source property's changes. To do this, the source object typically implements an interface named INotifyPropertyChanged. When the binding is created, it subscribes to the source's PropertyChanged event. When the source property changes, the binding is notified and it updates the target property.

In Windows XAML, UI elements are all DependencyObject instances. Most properties declared on such objects are DependencyProperty instances. Such properties will, under the cover, raise the PropertyChanged event and bindings can update the target and everyone's happy. A similar mechanism is also implemented in Xamarin.Forms with the BindableObject and the BindableProperty. In "classic" Xamarin.Android and Xamarin.iOS there are no such things as DependencyObjects and DependencyProperties however. Even though the INofityPropertyChanged interface is available, the UI elements of Android or iOS do not implement this interface. This is why the binding frameworks used in “classic” Xamarin need to use different mechanisms to listen to property changes on the source UI elements. Instead, they do this using the built in events, such as the TextChanged or CheckedChange (Android), ValueChanged (iOS) etc.

The linker conundrum

The Xamarin framework works by building C# / .NET code to unmanaged code suitable to run on iOS or Android platforms. In order to keep the executable’s size reasonable, all referenced assemblies (which are packed in the binary and deployed to the target device) are going through a linker which removes elements that are not used from the end executable. That sounds like a great idea to reduce binary size, but unfortunately for us, the linker is not super clever. For more information about the linker, you can check this page in the Xamarin documentation.

For instance, if the linker sees code such as this, it will know for sure that the CheckedChange event is used and it won’t remove it from the final executable.

MyCheckBox.CheckedChange += (s, e) =>
{
    // Do something
};

If however the code looks like this, the linker cannot know for sure what is happening at runtime. As such, it errs on the "aggressive" side and removes the event, even if the binding framework needs this information at runtime to handle the event.

this.SetBinding(
    () => MyCheckBox.Checked,
    () => MyTextView.Text)
    .UpdateSourceTrigger("CheckedChange");

As a result, when the code is ran, the CheckedChange event cannot be found and the binding framework throws an exception.

No problems in Debug mode

What can be confusing with this issue is that it only occurs in release mode. When the code is built in the debug configuration, nothing happens. This is because the linker is normally much less aggressive in this configuration.

The linker issue in the MVVM Light binding system

In MVVM light bindings, the UI element events are used by the Binding.UpdateSourceTrigger and Binding.UpdateTargetTrigger methods. For instance, the following code creates a OneWay binding between a Checkbox (source) and a TextView (target) in Android. Depending on the value of the Checkbox, the TextView will show the text True or False.

this.SetBinding(
    () => MyCheckBox.Checked,
    () => MyTextView.Text)
    .UpdateSourceTrigger("CheckedChange");

In Xamarin.Android (not in iOS at the moment), MVVM light will help you by automatically subscribing to the TextChanged event for a TextView, or the CheckedChange event for a Checkbox. So in this framework,the following code will create the exact same result at runtime.

this.SetBinding(
    () => MyCheckBox.Checked,
    () => MyTextView.Text);

This explains why the linker issue can happen even though these events are not explicitly mentioned in the code.

TwoWay binding

The same problem can also occur on events raised by the target of a binding, if the binding is in TwoWay mode. For example in Android:

this.SetBinding(
    () => MyCheckBox.Checked,
    () => MyTextView.Text,
    BindingMode.TwoWay)
    .UpdateSourceTrigger("CheckedChange")
    .UpdateTargetTrigger("TextChanged");

Same issue in commanding

The exact same problem occurs when using commands. For example, the following code may throw the discussed exception in release mode for the exact same reason.

MyButton.SetCommand(
    "Click",
    Vm.MyCommand);

Solving the issue

There are two possible ways to avoid this issue.

Changing the linker's setting

The first possible solution is to change or disable the linter's settings. To do this, follow the steps:

  • Open the project's properties.
  • Select the Android Options.
  • Select the linker tab.
  • Change the Linking option to None.
20150314001

In an iOS application, the same option is under:

  • Project properties.
  • Select the iOS Build options.
  • Select the General tab.
  • Change the Linker Behavior to Don’t link.
20150314002

This solution is not very satisfying because the size of the executable is going to grow quite a lot if assemblies are not linked. This is why it is preferred to use the second solution.

Using the event

Like we said, the linker is not very clever. It is easy to fool it by actually subscribing to the event and thus preventing it to be removed. For example:

MyCheckBox.CheckedChange += (s, e) => {};
MyTextView.TextChanged += (s, e) => {};

this.SetBinding(
    () => MyCheckBox.Checked,
    () => MyTextView.Text,
    BindingMode.TwoWay)
    .UpdateSourceTrigger("CheckedChange")
    .UpdateTargetTrigger("TextChanged");

MyButton.Click += (s, e) => {};

MyButton.SetCommand(
    "Click",
    Vm.MyCommand);

Conclusion

The linker in Xamarin is a necessary evil that reduces the size of the executable, but can create issues if you don’t pay attention. Hopefully this article helps to understand what is happening and how to solve it. Of course subscribing an empty event handler is not the most elegant solution but it has the advantage to solve the issue in an easy and pragmatic manner.