Xamarin.Forms effects
Xamarin.Forms effects are an elegant bridge between the cross-platform domain and the native domain. Effects are generally used to expose a certain platform behavior or implementation of a given native control through the shared domain so that a completely new custom native control is not needed for the implementation.
Similar to the Xamarin.Forms views/controls, effects exist on both the shared domain and the native domain with their abstraction and implementation, respectively. While the shared domain is used to create a routing effect, the native project is responsible for consuming it.
For instance, let's assume the details that we receive for our product items actually contain some HTML data that we would like to present within the application. In this case, we are aware of the fact that the Label element on Xamarin.Forms is rendered with a UILabel in iOS and TextView in Android. While UILabel provides the AttributedString property (which can be created from HTML), the Android platform offers the intrinsic module for parsing HTML. We can expose these platform-specific features using an effect and enable the Xamarin.Forms abstraction to accept HTML input. Let's get started:
- Create the routing effect that will provide the data for the platform effects:
public class HtmlTextEffect: RoutingEffect
{
public HtmlTextEffect(): base("FirstXamarinFormsApplication.HtmlTextEffect")
{
}
public string HtmlText { get; set; }
}
- Now, we can use this effect in our XAML:
<Label Text="{Binding Description}">
<Label.Effects>
<effects:HtmlTextEffect
HtmlText="<b>Here</b> is some
<u>HTML</u>" />
</Label.Effects>
</Label>
Without the platform implementation of this routing effect, the label will still display the binding data (in this case, encoded HTML text).
- Now, we need to implement the iOS effect that will parse the HtmlText property of our effect. Platform effects are mainly composed of two main components: registration and implementation:
[assembly: ResolutionGroupName("FirstXamarinFormsApplication")]
[assembly: ExportEffect(typeof(HtmlTextEffect), "HtmlTextEffect")]
namespace FirstXamarinFormsApplication.iOS.Effects
{
public class HtmlTextEffect: PlatformEffect
{
protected override void OnAttached()
{
}
protected override void OnDetached()
{
}
}
}
The effect that's registered with ResolutionGroupName of the ExportEffect attributes will be used in the runtime environment to resolve the routing effect that was implemented in the first step. In order to modify the native control, you can use the Control property of PlatformEffect. The Element property refers to the Xamarin.Forms control that requires this effect.
- Now, we need to implement the OnAttached method (which will be executed when PlatformEffect is resolved):
protected override void OnAttached()
{
var htmlTextEffect = Element.Effects
.OfType<Client.Effects.HtmlTextEffect>
().FirstOrDefault();
if(htmlTextEffect != null && Control is UILabel label)
{
var documentAttributes = new NSAttributedStringDocumentAttributes();
documentAttributes.DocumentType = NSDocumentType.HTML;
var error = new NSError();
label.AttributedText = new NSAttributedString(htmlTextEffect.HtmlText, documentAttributes, ref error);
}
}
- A similar implementation for the Android platform will create the HTML rendering of the controls:
protected override void OnAttached()
{
var htmlTextEffect = Element.Effects
.OfType<Client.Effects.HtmlTextEffect>
().FirstOrDefault();
if (htmlTextEffect != null && Control is TextView label)
{
label.SetText(
Html.FromHtml(htmlTextEffect.HtmlText,
FromHtmlOptions.ModeLegacy),
TextView.BufferType.Spannable);
}
}
While we have managed to display HTML content on our Xamarin.Forms view, the value we have used is still not bindable. With a little restructuring, and by using attached properties (that is, attached behavior), we can use the data binding and effects together.