August 2017

Volume 32 Number 8

[Xamarin.Forms]

How Xamarin.Forms Customization Took an FAA Drone App Higher

By Dan Hermes | August 2017

More than 1 million drones are in the hands of recreational flyers. People are taking unprecedented video footage of events, geography and nature with drone cameras. Commercial drone flyers are conducting inspections of structures and surveying land in a way that’s changing their industries. All these drones in the air have become the concern of the Federal Aviation Administration (FAA), which has responded with a strategy and a series of new regulations aimed at helping flyers operate safely and legally.

Of course, there’s an app for that. It’s called B4UFLY, and it’s written in Xamarin.Forms (note: B4UFLY is used with permission from Network Designs and the FAA). Drawing upon FAA airport and special location data, the app provides flyers with an interactive map and real-time status updates depending on their position or their planned flight. The status reflects the level of flight safety and legality and helps the flyer find areas away from airports and restricted airspace. The app, as shown in Figure 1, has been downloaded more than 300,000 times and is in its second year of updates.

B4UFLY Planning Mode Helps People Find Places to Fly Their Drones
Figure 1 B4UFLY Planning Mode Helps People Find Places to Fly Their Drones

The beauty of this Xamarin.Forms implementation is just how much of it is truly cross-platform. Of the 25 screens in the app, only one requires platform-specific customization. Many, if not most, mobile app requirements today include a cross-platform mandate. If such an app plan is mostly data entry and display, standard navigation and UI, and minimal graphics and animation, then it should be considered a strong candidate for development using Xamarin.Forms.

What Is Xamarin.Forms?

Xamarin.Forms is a library of cross-platform UI classes built atop Xamarin.Android and Xamarin.iOS that also binds directly to the native Universal Windows Platform (UWP), as shown in Figure 2. This provides a cross-platform set of UI components that render in each of the three native OSes.

Xamarin Libraries Bind to Native OS Libraries
Figure 2 Xamarin Libraries Bind to Native OS Libraries

Xamarin.Forms provides a cross-platform library of pages, layouts, and controls and is a great place to begin building an app quickly. There are two ways to create UIs in Xamarin.Forms: either in C# using the rich Xamarin.Forms API or using Extensible Markup Language (XAML), a declarative markup language created by Microsoft.

What Does the Xamarin.Forms Solution Look Like?

The B4UFLY solution contains four projects. The B4UFly project contains the Xamarin.Forms markup and code. The b4ufly.Droid project contains the Android-specific code and the b4ufly.iOS project is the iOS piece of the solution. B4UFly_UITEST contains scripts for UI testing, which can be done on a local computer or, ultimately, on Xamarin Test Cloud.

The Xamarin.Forms project, called B4UFly, contains cross-platform UI code written using XAML with C# codebehind and the Xamarin.Forms library. Cross-platform business logic and data access code is housed in the UTILS folder. App.cs is the initialization file for the Xamarin.Forms app.

Each platform-specific project has its own startup file for the respective OS. The Android project contains a startup file called MainActivity.cs, which defines an activity class inherited from Xamarin.Forms.Platform.Android.FormsApplicationActivity.

The iOS project contains a startup file called AppDelegate, which inherits from Xamarin.Forms.Platform.iOS.FormsApplicationDelegate.

Once a Xamarin.Forms project is created, development of the UI can follow.

Dynamic Layouts

B4UFLY makes use of all of the standard Xamarin layouts, including StackLayout, AbsoluteLayout and Grid. Xamarin.Forms Layouts can also be employed to create dynamic layouts with content that changes in real time. This isn’t about data binding, although that’s possible, as well. This is about modifying the structure and appearance of the screens themselves.

The two most important screens in the app are the map and the status page. The map is where the flyer’s GPS position is determined, and where surrounding locations and flight restrictions and airports are displayed. The map is also where a pin can be dropped, in something called Planning Mode, so the flyer can determine if it’s safe to fly there.

The status page (Figure 3) tells the user if it’s safe to fly. There are three main statuses: yellow, orange and red. (There’s no green because of lawyers.) Each of these statuses is reflected on the status page by a different status icon, by the text in the header and the color of the header’s background, as well as the text that’s displayed on the page to explain the status. Even the additional info buttons at the bottom of the page can change. The entire status page is dynamic.

Xamarin.Forms provides several ways to change content in midstream, providing dynamic content modifiable in real time. The first way is to modify existing layouts and their elements. The second is to show and hide elements. The third way is to add and remove elements from the page using C#. B4UFLY employs all three of these approaches in the status screen.

Modifying layouts begins with a layout to modify, created using XAML in this case, though it could just as easily be created using C#. This example is a StackLayout containing a status bar at the top of the map containing a status icon called topStatusIcon:

<StackLayout x:Name="topStatusIconHolder" Orientation="Horizontal"
  VerticalOptions="FillAndExpand" HorizontalOptions="StartAndExpand" 
  Padding="0, 5, 5, 0" BackgroundColor="White" >
  <Image x:Name="topStatusIcon" Aspect="AspectFit" Source="Blank.png"
    VerticalOptions="CenterAndExpand"
    BackgroundColor="Transparent" HorizontalOptions="CenterAndExpand"
    HeightRequest="50" WidthRequest="50" />
</StackLayout>

Depending on the user’s flight location, the status can change to fly or no-fly. This example shows a no-fly situation and the text and icon are updated to reflect the restriction:

if (safeToFlyResult.isInForbiddenZone == true)
{
  topStatusTextHolder.BackgroundColor = Color.White;
  topStatusText.Text = "Flight Prohibited";
  topStatusText.IsVisible = true;
  topStatusIcon.Source = ImageSource.FromFile("no_drone_zone.png");

Showing and hiding elements begins with a XAML layout, the “DO NOT FLY” status in this case:

<StackLayout x:Name="stackForbiddenToFly" Orientation="Vertical" IsVisible="false" 
  Padding="10, 20, 10, 5" VerticalOptions="Start">
  <Label x:Name="forbiddenDoNotFlyText" Text="DO NOT FLY YOUR AIRCRAFT" 
    TextColor="#DA4E5B"  
    FontSize="22" FontAttributes="Bold" HorizontalOptions="Center"
    HorizontalTextAlignment="Center" />
</StackLayout>

When the status is determined to be no-fly because a location has been chosen where drone flight is prohibited, the StackLayout stackForbiddenToFly is made visible (as shown in Figure 3):

Status Page in a No-Fly Area
Figure 3 B4UFLY Status Page in a No-Fly Area

if (safeToFlyResult.isInForbiddenZone == true)
{
  stackForbiddenToFly.IsVisible = true;
  ...

The final dynamic UI approach is the physical removal of elements from a layout using C# code. Here’s an example of a layout and button being removed from a layout’s collection of child elements:

stackCurrentLocationTop.Children.Remove (refreshComboStack);
stackCurrentLocationTop.Children.Remove (dismissImgBtn);

Add a layout to a layout’s children:

stackCurrentLocationTop.Children.Add 
       (refreshComboStack, 3, 4, 0, 1);

Those are the three main approaches to dynamic UI: modify existing layouts and their elements, showing and hiding elements, and adding and removing elements and layouts from layout collections using C#.

Xamarin.Forms has become an increasingly easier choice with the outstanding support for Xamarin.Forms customization, providing access to native UI features. A good rule of thumb is that you don’t want to have to customize (by platform) more than 20 percent to 30 percent of your app. More than that and you should use a platform-specific option, such as Xamarin.Android or Xamarin.iOS. So what does it mean to customize a Xamarin.Forms app?

Customizing Your App Using Xamarin.Forms

Before Xamarin.Forms was released, I would code my mobile app’s cross-platform business logic and data layer in C#. I would then build my UIs with complete access to the underlying native SDKs, but I’d need to make UIs for each platform using Xamarin.iOS, Xamarin.Android or Windows 10 SDK.

So when it was first announced that with Xamarin.Forms you could build your mobile UI only once and compile for iOS, Android and the UWP, my heart skipped a beat. That’s because it’s what I always longed for: an end-to-end cross-platform development experience.

However, I knew just how deep Xamarin already went when it came to native UI and I wondered: “What if I need something that Xamarin.Forms can’t do?”

I asked everyone I knew to explain exactly what Xamarin.Forms could do and what it couldn’t do, and I received many terrific responses that helped me better understand Xamarin.Forms, but no one could really answer my question. So, I wrote a book to answer it: “Xamarin Mobile Application Development” (Apress, 2015). And here’s a spoiler: Use custom renderers.

Custom renderers give you the ability to punch down through the Xamarin.Forms abstraction and gain direct access to Xamarin.Android, Xamarin.iOS and the UWP. This means access to the native UI SDKs: iOS UIKit, Android SDK and Windows 10 SDK. You can create platform-specific views and pages in the platform-specific project anytime you need to use functionality in native iOS, Android and Windows.

Using the Xamarin.Forms built-in Dependency Injection, you initialize and reference the custom UI class and Xamarin pulls it out of the appropriate platform’s project for you. That’s how the map page was built in B4UFLY.

But what if you just want to change one or two properties or events and don’t need an entire custom UI class?

Enter Effects. Coding an entire UI renderer class for each platform can be excessive. Sometimes all that’s needed is a tweak to a single control element, such as a drop shadow on a label. While custom renderers expose an entire platform-specific class, Effects exposes just its properties. The entire element needn’t be subclassed, though a platform-specific class is necessary. A Xamarin.Forms effect offers this precision approach to platform-specific UI customization.

What if all you really need is a native control on your Xamarin.Forms layout?

Take the plunge and declare a platform-specific control, sometimes called a “native control,” though it’s a Xamarin control and not truly native. Instead of coding overly customized custom renderers, declare native views from Xamarin.iOS, Xamarin.Android or the UWP directly into your Xamarin.Forms layouts. Using a shared project and conditional compilation, include platform-specific UI libraries in your C# UI classes where you can reference them as directly as if you were coding in the native platform. Set properties and event handlers on these views and use them side-by-side with Xamarin.Forms views, in both C# and XAML.

Xamarin.Forms development gives you the ease of cross-platform development using C# and a single UI library with a solid foundation of customization options for those times when you really need native features. Use custom renderers to build platform-specific UI classes using Xamarin.iOS, Xamarin.Android and the UWP. Use effects to access platform-specific properties. And when you can’t do without the real thing, declare a native view in your Xamarin.Forms layout.

Custom Renderers—B4UFLY Map

The B4UfLY map page is the only page of more than 25 in the app that requires customization. That ratio of 25:1 generic Xamarin.Forms pages to customized page makes this app a strong case study for Xamarin.Forms.

The map uses your current location and provides immediate surrounding flight restrictions and warnings, as shown in Figure 4.

Map Page at Lexicon Systems Office in Beverly, Mass.
Figure 4 Map Page at Lexicon Systems Office in Beverly, Mass.

A variation on the map page is Planning Mode, which permits the dropping of a pin to determine the restrictions and flight statuses of hypothetical locations, as shown in Figure 5. Note the icon in the upper left indicating “no-fly” due to a nearby controlled airspace (the “C” icon).

Planning Mode Page in San Francisco, Calif.
Figure 5 B4UFLY Planning Mode Page in San Francisco, Calif.

Xamarin.Forms binds to only a fraction of the features available in the complete platform-specific UI libraries (iOS Webkit, Android SDK and Windows 10 SDK). Fortunately, Xamarin.Forms exposes the mechanism whereby cross-platform views are converted into platform-specific views. This mechanism is called rendering. By creating your own custom renderers, you get full access to platform-specific features buried deep within each view.

Custom renderers are a bridge between Xamarin.Forms and Xamarin platform-specific libraries, Xamarin.iOS, Xamarin.Android and Windows 10 SDK. Think of a custom renderer as a way to access and extend the binding between Xamarin.Forms and the platform-specific elements.

Project requirements call for features not possible with the out-of-the-box Xamarin.Forms.Maps library, including the placement of icons and colored areas around each icon to delimit certain airspaces on the map. Custom rendering to the rescue! Beginning with MapPage, created by inheriting ContentPage, you can create a foundational class, which you can use to customize its renderer for each platform, letting you code custom graphics separately for iOS and Android:

namespace b4ufly.iOS
{
  public partial class MapPage : ContentPage
  {
    public static MapPage me = null;
    public static MyMap map = null;
    public static Boolean plannerModeOn = false;

Once you have a custom element, MapPage, then you need to create the custom renderers for each platform, iOS and Android in B4UFLY, although you can also do this for UWP. Renderers realize a view on the native platform. You create your own renderer by inheriting from the standard MapRenderer, beginning with iOS:

[assembly:ExportRenderer (typeof(MyMap), typeof(MyMapRenderer))]
namespace b4ufly.iOS
{
  public class MyMapRenderer : MapRenderer, MapExtension
  {

MyMapRenderer draws the locations on the map that drone flyers need to be aware of: airports, controlled airspace, military facilities and the like. The renderer draws both icons and surrounding colored areas denoting the important airspace. These types of graphics are handled slightly differently in iOS than in Android. The Android map renderer uses a similar approach to the one used for iOS:

[assembly: ExportRenderer (typeof(MyMap), typeof(MyMapRenderer))]
namespace b4ufly.Droid
{

  public class MyMapRenderer : MapRenderer, MapExtension,  
    GoogleMap.IOnCameraChangeListener, GoogleMap.IOnMarkerDragListener,  
    GoogleMap.IOnMarkerClickListener
  {

Once you create the renderers, it’s time to use them. Based on the MyMap data type, which uses the MyMapRenderer, the following statement instantiates a platform-specific map:

map = new MyMap(MapSpan.FromCenterAndRadius(new Position(0, 0), Distance.FromMiles(1.0)))

The built-in Inversion of Control (IoC) mechanism in Xamarin.Forms uses the renderer from the platform project currently being built. By adding platform-specific map references, you could explicitly instantiate an Apple Mapkit in the iOS renderer and a Google Map in the Android renderer.

Customization of Xamarin.Forms elements leads you to a different view of the solution architecture, with custom renderers residing in the middle platform-specific UI layer, as shown in Figure 6.

Xamarin.Forms UI
Figure 6 Xamarin.Forms UI

Custom renderers are powerful and thorough in their implementation as platform-specific enablers of Xamarin.Forms UI elements. Custom renderers are, however, heavy artillery. If you want something more tactical, like merely customizing a property on a Xamarin.Forms control, consider an “effect.”

Effects

Effects provide access to individual platform-specific properties of controls and can be parameterized. To create an effect, first create a class that is a subclass of the RoutingEffect class. Mind the method overrides and attributes. Then use the effect in your app.

In addition to exposing properties, effects also have the capacity to pass parameters to those properties and define events on Xamarin.Forms controls. You pass parameters to the effect using Attached Properties or the Common Language Runtime (CLR). The following example uses the CLR to bind properties to the effect and creates the DropShadowEffect in the Xamarin.Forms project:

public class DropShadowEffect : RoutingEffect
{
  public Color Color { get; set; } 

  public DropShadowEffect () : base ("FAA.DropShadowEffectLabel")
    {           
    }
}

This label effect provides a color property for the shadow and references the platform-specific implementation of the DropShadowEffectLabel in its base class.

You implement the effect in a platform-specific project, similarly to a custom renderer, although implementation is optional in each platform. Once per project, you add a ResolutionGroupName attribute containing your company name to avoid collisions with other effects of the same name. Each Effect class is subclassed from PlatformEffect and needs an ExportEffect, which registers the effect with Xamarin.Forms. Figure 7 shows an implementation on iOS in the Xamarin.iOS project.

Figure 7 iOS implementation of DropShadowEffectLabel

[assembly:ResolutionGroupName ("FAA")]
[assembly:ExportEffect (typeof(DropShadowEffectLabel), "DropShadowEffectLabel")]
namespace b4ufly.iOS
{
  public class DropShadowEffectLabel : PlatformEffect
  {
    protected override void OnAttached ()
    {
      try {
        var effect = 
         (DropShadowEffect)Element.Effects.FirstOrDefault 
           (e => e is DropShadowEffect);
        if (effect != null) {
          Control.Layer.ShadowColor = effect.Color.ToCGColor();
          Control.Layer.CornerRadius = 5;
          Control.Layer.ShadowOffset = new CGSize (5, 5);
          Control.Layer.ShadowOpacity = 1.0f;          }
    } catch (Exception ex) 
    {
      Console.WriteLine ("Cannot set effect property. Error: ", ex.Message);
    }
    }

    protected override void OnDetached ()
    {
    }
}

Control is an iOS UIView.PlatformEffect that exposes these methods and which must be overridden:

  • OnAttached—customize the control here
  • OnDetached—perform cleanup (for example, deregister events)

Next is the Android implementation, similar to the iOS effect, except that the label control is the Android-specific TextView, as shown in Figure 8. The TextView control is typed explicitly to access the SetShadowLayer method.

Figure 8 Android Implementation of DropShadowEffectLabel

[assembly:ResolutionGroupName ("FAA")]
[assembly:ExportEffect (typeof(DropShadowEffectLabel), "DropShadowEffectLabel")]
namespace b4ufly.Droid
{
  public class DropShadowEffectLabel : PlatformEffect
  {

    protected override void OnAttached ()
    {
      try {
        var control = Control as Android.Widget.TextView;
        var effect = 
         (DropShadowEffect)Element.Effects.FirstOrDefault 
           (e => e is DropShadowEffect);
        if (effect != null) {
          Android.Graphics.Color color = effect.Color.ToAndroid ();
          control.SetShadowLayer (5, 5, 5, color); 
          // params: radius, offsetX, offsetY, color
        }
      } catch (Exception ex) {
        Console.WriteLine ("Cannot set effect property. Error: ", ex.Message);
      }
    }

    protected override void OnDetached ()
    {
    }
  }
}

Once the effect is in place, it’s time to invoke it. First, a control needs to be declared in XAML or C#. You then attach the effect to the control by adding it to the control’s effects collection. The following example shows the XAML approach with an Entry control declared in XAML with the DropShadowEffect added to the control’s Effects collection and the Color property set to black:

<Label Text="Label with Shadow" ... >
  <Label.Effects>
    <local:DropShadowEffect Color="Black">
    </local:DropShadowEffect>
  </Label.Effects>
</Label>

Using C# instead of XAML, the label with attached effect can be created, as shown here:

var label = new Label {
  Text = "Label with Shadow",
  ...
};
label.Effects.Add (new DropShadowEffect {
  Color = Color.Black,
});

Tactical customization using effects lets you make specific changes to the Xamarin.Forms controls, but sometimes changing certain properties and methods just isn’t enough. When you want to use a lot of features of a native control, then you wind up doing a lot of custom effects coding.

Native View Declaration

Sometimes you want complete control of the UI. Thankfully there’s now a way to get this in Xamarin.Forms via native view declaration. Declared native controls are incredibly powerful, but are not without limitations. They’re easiest to use in XAML, secondarily in C# using a Shared Project (which is called native embedding), though it’s possible but not easy or recommended to use them in a Portable Class Library (PCL). A lot of projects use PCLs and that often means native views are best used in XAML, and that’s the approach I’ll cover here.

There are two steps in declaring a native view in XAML. First, specify the namespace for each native source. Second, declare the native view. Figure 9 shows an example, using the label control. It begins with the basic XAML page and defines the namespaces for iOS, Android and Windows (shown in bold code).

Figure 9 Native Control Namespace Declarations

<?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:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
  xmlns:androidWidget="clr-namespace:Android.Widget;assembly=
    Mono.Android;targetPlatform=Android"
    xmlns:formsandroid="clr-namespace:Xamarin.Forms;assembly=
    Xamarin.Forms.Platform.Android;targetPlatform=Android"
  xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, 
    Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, 
    ContentType=WindowsRuntime;targetPlatform=Windows"
  x:Class="b4ufly.NativeView" >
  <ContentPage.Content>
  </ContentPage.Content>
</ContentPage>

Next, native views are declared in the Content property of the ContentPage. A UILabel for iOS, a TextView for Android and a TextBlock for Windows:

<ContentPage.Content>
  <ios:UILabel Text="This is an iOS UILabel" View.HorizontalOptions="Start"/>
  <androidWidget:TextView Text="This is an Android TextView" 
    x:Arguments="{x:Static formsandroid:Forms.Context}" />
  <win:TextBlock Text="This is a Windows TextBlock"/>
</ContentPage.Content>

Those are the three approaches to Xamarin.Forms customization: custom renderers, effects and native view declaration. Custom renderer is a heavyweight option offering a lot of flexibility, while effects provides a surgical approach to customization. Native view declaration is the nuclear option, circumventing Xamarin.Forms entirely.

Wrapping Up

You’ll eventually need more from Xamarin.Forms than it gives you out-of-the-box, just like I did with B4UFLY. When complex tasks or designs are required by Xamarin.Forms, virtually anything is possible using Xamarin.Forms customization. Customization provides access to the lower-level, platform-specific, screen-rendering classes called “renderers,” which use platform-specific controls to create all Xamarin.Forms screens. Any Xamarin.Forms screen can be broken into platform-specific screens, classes, controls and properties using this approach.

A lighter-weight approach is to use effects to access platform-specific properties and events. You can also use entire native controls on your Xamarin.Forms pages using native view declaration.

This means that you can write a Xamarin.Forms page or app and customize it by platform. Use customization sparingly, or risk a fragmented UI code base that probably should have been written entirely as a platform-specific UI. Used judiciously, customization can turn your basic, lackluster product into a versatile, unique, popular app.

In B4UFLY, the FAA’s investment in Xamarin.Forms continues to pay off because of the many ongoing enhancements that are generic to the many cross-platform text-based pages. The platform-specific map page contains some cross-platform elements, but much of that page requires platform-specific customization. This Xamarin.Forms architecture is extensible and development times and costs are lower because of it; the significant code reuse is practical and elegant.


Dan Hermes is a Xamarin MVP, a Microsoft MVP, and author of “Xamarin Mobile Application Development.” He is principal of Lexicon Systems, a Boston-­based consultancy building award-winning mobile apps and helping companies build their own successful apps. Follow his blog at mobilecsharpcafe.com, on Twitter: @danhermes or contact him at dan@lexiconsystemsinc.com.

Thanks to the following technical expert for reviewing this article: Jesse Liberty


Discuss this article in the MSDN Magazine forum