3 Xamarin.Forms Tricks to Maximize Shared UI Code in Mobile Development
In 2014 I was tasked with creating a relatively simple iOS/Android app from scratch using this newfangled mobile app development technology called Xamarin.Forms. I was on the project for a total of three days. I had years of C#/WPF experience but none in Xamarin, and this Forms business was the confounding icing on the proverbial confusion cake. I got essentially nothing done in those three days: there was almost no documentation, extremely low StackOverflow, etc. saturation, and I just couldn’t understand why you would use a cross-platform framework that forces you to write a separate rendering class for each platform in special cases. And every case was a special case! I had enough time to grasp the depth of my plight and not much more.
Fast forward to the beginning of this year wherein I was pulled onto a newly-formed team working on mobile app development. They told me Xamarin.Forms is the mobile tech of choice. THE HORROR.
But it turns out the learning environment has become much more rich. In the meantime I’ve also had over a year and a half of Xamarin (with native UI) development to get me learning Forms-specific concepts more quickly. I now realize that, even though I’m still writing some renderers to get each UI control looking juuust right, Xamarin.Forms is all about minimizing platform-specific code to certain controls and otherwise having completely shared UI code. 90+% shared code comes surprisingly easily.
There are 3 main constructs I’ve been using that are critical to the Xamarin.Forms experience. I called them weird in the title to sound like a weather.com ad, but they’re actually quite happy path to making your app look decent.
When diving into Xamarin.Forms for the first time, you discover that it’s extremely, incredibly similar to WPF development. Except it’s much more limited (yesterday, my colleague Brian Ball declaimed that “Xamarin.Forms is like Silverlight’s dumb cousin”, and Silverlight is already WPF’s dumb cousin). That means there are fewer controls provided by the framework, and those controls have way fewer properties available for modification than you would expect.
Renderers are what you will likely stumble across first when searching for how to dig further into a control’s properties. Let’s say you want your drop downs, a.k.a. Pickers, to look indistinguishable from a normal Label control. Depending on the platform, the Picker will render out of the box with an underline, a white background, and/or a border, and have no properties to change those or even set the text alignment, color, or size. Our UX team gives us a beautiful mockup to follow, and there’s no way in the XAML code to get anywhere close to that look.
What you can do is write a subclass for Picker in your shared project. You can give it extra properties or do some fancy initialization logic, or nothing. You then need to go into each platform’s project and create a renderer class for that subclass, and each renderer also has to be a subclass of the PickerRenderer in this case. You rubber band all of this together with an ExportRenderer attribute at the namespace level. Finally, you have to implement an override of OnElementChanged, which will intercept the control you want to dig into before it’s rendered, and there, finally you can access a variable called “Control” and set native properties on it to get it looking like the label you want.
Note that this requires you to declare and use your MyLabelLookingPicker control every place in XAML you want it to look like a label, and every MyLabelLookingPicker will use the renderer you created. Additionally, you don’t actually need to put a renderer in every platform’s project. If there isn’t one in the iOS project for example, your control will silently fall back to the normal PickerRenderer when rendering on iOS.
Effects are similar to renderers but are a breath of fresh air in comparison because they’re a little more fast and loose. You don’t need to subclass the control you want to modify, and you apply the effect to individual instances of that control. You can apply as many effects as you want to any control, which allows for awesome granularity.
Using the Picker as an example again, we could be heavy-handed and make a MakeMyPickerLookLikeALabelEffect class. You must create that class in the shared project and each platform project where you want the change the control. Again, you rubber band these together with a namespace-level attribute (ExportEffect). The class in the shared project contains any properties you want to add to the control and maybe some logic, and the platform-specific versions have override methods for OnAttached and OnDetached, like a Behavior class. OnAttached will run before the control is rendered, so you can set properties on the Control object and change its appearance with native access.
The main power with Effects over Renderers is their potential for granularity. Want a Picker to have a transparent background and an underline on some pages but have right aligned text and a visible background on others? Write small Effect classes that change only a few properties individually and combine them in the XAML differently to achieve different appearances. This prevents having a giant Renderer class with a ton of exposed properties that you only use a couple of at a time. I think I’ll realize the differences in power and use cases between these two controls much more going forward.
Outside of XAML and UI-related constructs, the main way to use platform-specific functionality (e.g. accessing the built-in email app, contacts, or camera) is through Xamarin’s DependencyService. Xamarin’s own explanation on this is very easy to understand, so I’ll just link it here. Basically, you can make platform-specific classes that share an interface for a specific function, then use DependencyService to get a reference for the correct platform’s class at runtime. I can’t imagine a simpler way to access full platform-specific functionality from the midst of your shared code.
Let’s wrap this up, Kimsey
Renderers and Effects together allow you to write small classes that modify only the properties you can’t access in XAML. For everything else UI, such as the oodles of ever-necessary layout and state code, there’s cross-platform XAML. Combine with that the DependencyService that lets you pinpoint powerful device functionality toward the backend, and your Xamarin.Forms toolbox is already getting pretty fleshed out.
Xamarin.Forms was a little scary to dive back into after my first, short bleeding edge attempt, but it really does allow you a very high ratio of easy-to-write shared .NET code. That, for the average .NET or mobile app developer, gets a lot of functionality on a lot of platforms very quickly once you learn the tricks.