Corrado's Blog 2.0

Online thoughts of a technology funatic

Using Xamarin Forms Effects

Version 2.1 of Xamarin Forms introduced Effects, a nice alternative to custom renderers when all you need is to tweak some properties of the platform native control, they should be seen as an alternative to a custom renderer not as a substitute.

Let’s quickly see how they work, let’s suppose we want to limit the amount of text that a user can type inside an entry, something that’s not natively possible with Xamarin Forms (at the time of this writing…)

Create a new Xamarin Forms project and upgrade Xamarin.Forms assemblies to latest stable version greater than 2.1 , in my case is 2.1.0.6529

image

Effects are a mix of platform specific code and code that resides in application PCL library, let’s start with the PCL and create a class that uses RoutingEffect  as base class:

public class MyEntryEffect : RoutingEffect { public MyEntryEffect() : base("MyCompanyName.EntryEffect") { } public int MaxLength { get; set; } }

As you can see the constructor of this class invokes base constructor passing the fully qualified name of the platform specific effect to be created (“MyCompanyName.EntryEffect” more details on this soon)

It’s time to apply our Effect (or as I prefer ‘extension’) to a Xamain Forms Entry, in my project I’ve added a MainView.xaml page and this is related XAML.

<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:demoEffects2="clr-namespace:DemoEffects2;assembly=DemoEffects2" x:Class="DemoEffects2.MainView"> <StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"> <Entry Placeholder="Try to type more than 5 chars.." > <Entry.Effects> <demoEffects2:MyEntryEffect MaxLength="5" /> </Entry.Effects> </Entry> </StackLayout> </ContentPage>

Quite easy to understand: We added our custom MyEntryEffect effect to Entry’s Effects collection and set it’s MaxLength property to 5.

Is now time to switch to platform specific projects and implement the code that reads the value of MaxLength property and applies this limit to platform specific control.

Let’s start with Android:

[assembly: ResolutionGroupName("MyCompanyName")] //Note: only one in a project please... [assembly: ExportEffect(typeof(EntryEffect), "EntryEffect")] namespace DemoEffects2.Droid { public class EntryEffect : PlatformEffect { protected override void OnAttached() { try { //We get the effect matching required type, we might have more than one defined on the same entry var pclEffect = (DemoEffects2.MyEntryEffect)this.Element.Effects.FirstOrDefault(e => e is DemoEffects2.MyEntryEffect); TextView editEntry = this.Control as TextView; editEntry?.SetFilters(new Android.Text.IInputFilter[] { new Android.Text.InputFilterLengthFilter(pclEffect.MaxLength) }); } catch (Exception ex) { //Catch any exception } } protected override void OnDetached() { } protected override void OnElementPropertyChanged(PropertyChangedEventArgs args) { base.OnElementPropertyChanged(args); try { if (args.PropertyName == "Your property name") { //Update control here... } } catch (Exception ex) { Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message); } } } }

Inside the Android project I have created an EntryEffect class that inherits from PlatformEffect and implemented two familiar overrides: OnAttached and OnDetached.

As you might expect the first is invoked when Effect is applied to the control and can be used to initialize the property of Android’s native control while OnDetached is called when the effect is removed and can be used to perform any cleanup. 

From inside this methods we have access to three fundamental properties:

Container: The platform specific control used to implement the layout.

      • Control: The platform specific counterpart of Xamarin Forms control.

Element: The Xamarin Forms control being rendered.

Since Effect can be added to Effects collection of any control the code must take this into consideration and degrade gracefully in case actions cannot be completed due to a control type mismatch.

Inside OnAttached we retrieve the PCL effect so that we can read it’s MaxLenght property and we cast the Control property to a TextView, if casting fails we simply do nothing otherwise we add a filter that limits the number of typed chars inside the TextView.

Even if not used in this sample, the code includes OnElementPropertyChanged override that can be used when you want to be notified when a property of the control changes (e.g. IsFocused) and do something when this happens.

Last, but absolutely not least come the two attributes ResolutionGroupName and ExportEffect

ResolutionGroupName : Allows you to define a custom namespace for you effects to prevent naming collision, it must be used once in the platform specific project.

ExportEffect: Is the name that’s used by initial effects discovery process and it accepts the type of the effect it is applied to and the name you want to export for discovery.

The concatenation of ResolutionGroupName and ExportEffect id is used by RoutingEffect class (see it’s base constructor in preceding code) for proper identification.

As for custom renderers is not necessary to implement the effect for each class, if undefined it simply gets ignored.

Here’s the iOS effect version:

[assembly: ResolutionGroupName("MyCompanyName")] //Note: only one in a project please... [assembly: ExportEffect(typeof(EntryEffect), "EntryEffect")] namespace DemoEffects2.iOS { public class EntryEffect : PlatformEffect { protected override void OnAttached() { try { //We get the effect matching required type, we might have more than one defined on the same entry var pclEffect = (DemoEffects2.MyEntryEffect)this.Element.Effects.FirstOrDefault(e => e is DemoEffects2.MyEntryEffect); UITextField editEntry = this.Control as UITextField; if (editEntry != null) { editEntry.ShouldChangeCharacters = (UITextField textField, NSRange range, string replacementString) => { // Calculate new length var length = textField.Text.Length - range.Length + replacementString.Length; return length <= pclEffect.MaxLength; }; } } catch (Exception ex) { //Catch any exception } } protected override void OnDetached() { } } }

Simpler, but more flexible, Xamain Forms effects represent a valid alternative to Renderers, I’m pretty sure that we’ll see many open source effects coming from th Xamarin community.

If you want to know more about Effects, this is the link to follow.

Happy Effecting.