Solving the “Event not found” issue in Xamarin #MVVMLight binding and commanding

MVVM, Technical stuff, Work, Xamarin
See comments

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.

Happy coding
Laurent

Previous entry | Next blog entry

Responses to “Solving the “Event not found” issue in Xamarin #MVVMLight binding and commanding”

  1. BenR Says:

    Very good explanation, thanks.

    I have one question though – do people actually care that much about a larger executable? Correct me if I’m wrong, but we’re talking about a few MBs difference right, maybe 20MB at most?

    Many apps I download are in the hundreds of MBs, usually due to their media content. Retina screens force much larger image files in general. I wouldn’t expect it matters until you get into the GB range. What’s the real impact of inflation under 100MB?

  2. Tips of the week 2015-03-23 | Basho on the .NET Says:

    […] Laurent Bugnion: Solving the ‘Event not found’ issue in Xamarin #MVVMLight binding and commanding […]

  3. Miguel Says:

    Hi Laurent!

    I’m following the app that you made in the Xamarin evolve 2014, the Flowers app. Using the second option I resolved the problem with the buttons but with the ListView itemclick It doesn’t work, do you know how can I make your app Flowers work in release mode correctly?..

    Thanks.

  4. lbugnion Says:

    Hi, can you let me know what the exact issue is? Thanks

  5. Wyatt Says:

    Do you think that a possible solution could be to include the binding framework dll in the Xamarin custom linker configuration file?

  6. lbugnion Says:

    That’s an interesting suggestion. I am having dinner with the Xamarin guys this week, let me ask them what they recommend

  7. John Says:

    Hi Laurent,

    we having achieving great results using MVVMLight with Xamarin iOS, but we’ve discovered one issue that we can’t work out how to get around. We find that bindings that are added whilst specifying an UpdateSourceTrigger stops our views being released from memory. We suspect an event handler is being held. We are aware of the Detach method, but this removes ALL bindings for a particular property, as far as we can tell. What if we want to remove specific bindings i.e. the binding on the particular view being closed, how do we do that?

  8. Erik Says:

    I also found that you can keep the “Link Framework SDKs Only” selected as long as you pass in the following additional mtouch argument:

    –linkskip=Xamarin.iOS

    This keeps the linker from removing the events that MVVMLight relies on. Sure, it pulls in a lot of extra unused symbols, but it generates a smaller binary than “Don’t Link” and you no longer have to += (s,e) => { } every event. Seems to me to be a good balance between the two approaches suggested in this article.

  9. lbugnion Says:

    Great feedback, thanks!

  10. Morgan Says:

    Hello, i found an issue with the code below (Release mode, link sdk assembly only)

    this.SetBinding(() => Vm.Password,() => PasswordBox.Text, BindingMode.TwoWay)

    the only workaround seems to be set “Use shared runtime” but i can’t create the app package.
    Help appreciated, thank you.

  11. lbugnion Says:

    Hi,

    What issue exactly?

    Note that to avoid early garbage collection of the binding, you should save it as a private member of the view, for example with

    _bindingsList.Add(this.SetBinding(….));

    where _bindingsList is a List<binding>

    Laurent

  12. Morgan Says:

    Hi,
    yes it’s right, i just cut that part of code, on my application i write
    _bindings.Add(
    this.SetBinding(
    () => Vm.Password,
    () => PasswordBox.Text,
    BindingMode.TwoWay));

    where “vm.password” is a string created with mvvm light code snippet in viewmodel and “passwordbox” is an EditText

    Morgan

    I found another workaround by set in “skip linking assemblies” “Mono.Android”, but the package from 10 MB becomes 32MB

  13. lbugnion Says:

    Can you send me a repro? laurent at galasoft dot ch. Thanks!

  14. dh Says:

    thanks

  15. Helen Says:

    I get the same error in iOS but I get it in debug mode as well and if I change the Linker behaviour to not Link then I get a build error to either update Xcode or enable the Linker. I will update Xcode but I’m not sure this is even the same problem as I get it in debug mode as well as I said.

    I am trying to bind to a UITextField but I keep getting a runtime error about an Event not Found exception with regards to “EditingChanged”. Any ideas why this might be?

  16. lbugnion Says:

    Hi,

    Do you have code that I can look at?

    Thanks
    Laurent

  17. Helen Says:

    Thanks for your response. I’m currently trying the following:
    _textEnteredBinding = this.SetBinding(
    () => PhoneNumberText.Text,
    () => Vm.TextEntered,
    BindingMode.TwoWay)
    .ObserveTargetEvent(nameof(UITextField.EditingChanged));

    Where TextEntered is a field in my ViewModel and PhoneNumberText is the name of the UITextField.

    I did an OSX update and tried some other syntax and I
    just get the “Set” method not found for “Text” RT error
    now. I have sent you my source code if you want to take a look. Thank you.

  18. Aloïs Says:

    Hi Laurent,

    I want to point out the fact that apps published to Apple Store need to be linked.

    IMO the best workaround would be to have an “Initialize” method that calls explicitly each of the elements that are stripped by the linker at compilation.

    Maybe you included that kind of mechanism since this blog post ?

  19. Richard Says:

    I encountered the same problem. As I was in the middle of a delivery process, I just turned off the linker. My app now weighs 450Mb lol…

  20. cnicolas Says:

    Hello Laurent,

    I am experiencing the exact same issue as Helen, my two way binding does not work on a textfield because of the missing set method.

    We currently have to disable the linker in order for the app to run on the device. Unfortunately, Apple requires us to link the app in order not to ship the whole Xamarin SDK (for instance, HomeKit must not be included if it is not used within the app)

  21. Piotr Says:

    Hello

    That may sound strange but when I use this code:
    _bindings.Add(this.SetBinding(
    () => _viewModel.Username,
    () => TextboxUsername.Text,
    BindingMode.TwoWay));
    I get ‘Set’ method not found RT exception.

    When I change order of parameters like that:
    _bindings.Add(this.SetBinding(
    () => TextboxUsername.Text,
    () => _viewModel.Username,
    BindingMode.TwoWay));
    then problem with event appears which is linker problem I guess, and can be sorted by subscribing to event.

    Also I have got RelayCommand connected to button. I did subscribe to TouchUpInside and I have CanExecute method dependant from Username property. Problems are in both debug/release mode on iPhone. Button is not deactivated when username is empty.
    All works fine on simulator.
    Any idea how can I fix that button?

    I am using Xamarin, iOS – storyboard approach. MvvmLight 5.3.0.0
    When I disable linker all is fine on physical device as well, but that looks like no option in regards of publishing the app in App Store.

  22. urs Says:

    Hi Laurent

    Thank you very much for your efforts to the MVVM Light framework. I would like to ask again what Aloïs already wanted to know half a year ago. Is there a better solution “today” than registering the empty events?

    Thanks for a quick response.
    -urs

  23. Jorge Says:

    I am having the same issue with the following code. I do not want to break linking – Have not been able to figure out a solution. I am using latest Xamarin, MVVMLight and VS2007 Community:

    MySeekBar.ProgressChanged += (s, e) => {};

    var seekBarBinding = new Binding
    ( myCategory,
    () => myCategory.Point,
    mySeekBar,
    () => mySeekBar.Progress, BindingMode.TwoWay).ObserveSourceEvent(nameof(mySeekBar.ProgressChanged));

  24. Jorge Says:

    Sorry meant to say VS2017

  25. Jorge Says:

    I was able to make it work:

    I swapped the source and the target. So now the code looks like:

    var seekBarBinding = new Binding
    ( mySeekBar,
    () => mySeekBar.Progress,
    myCategory,
    () => myCategory.Point,
    BindingMode.TwoWay).ObserveSourceEvent(nameof(mySeekBar.ProgressChanged));

    Hopeful this is useful to anybody

  26. Chantraine Christophe Says:

    Hey,

    For now, I just created my custom textfield class that inheriting of UITextfield.
    In my common init method inside this class, I juste wrote
    this.EditingChanged += (s, e) => { };

    Then, all is fine because the linker don’t “skip” this event.

Comments for Solving the &ldquo;Event not found&rdquo; issue in Xamarin #MVVMLight binding and commanding