One of the first decisions a team faces when beginning a cross-platform mobile app in Xamarin is whether to use Native or Forms. Both options have tradeoffs. Using Xamarin.Forms, you maximize code share for lower time-to-market by writing platform-agonistic user interface (UI) code against a shared abstraction layer. In a Xamarin Native app, you work directly with each platform's traditional UI paradigms, increasing flexibility at the expense of lower code share. A variety of factors - both technical and organizational - influence which approach is right for you; but what if these change over the life of your app? I'll relate one such situation: an app that started out in Xamarin Native, in conditions that, over time, became more suited to the strengths of Xamarin.Forms. In this article, I'll describe the impetus for change and how Xamarin's Native Forms feature enabled a progressive, low-risk introduction of Forms without compromising on the upsides. I'll walk through techniques for making a progressive migration seamless (both from a user's and a developer's point of view), as well as the benefits that such an approach offers.
Xamarin Native? Xamarin.Forms?
The delivery of practically every technology project involves architectural design decisions. In some cases, these are essentially mandated either by standards or a lack of viable alternatives. In other cases, they might be influenced by a wider range of internal or external factors. In this article, I'll look at the journey of one organization's mobile team to adapt their project - a cross-platform mobile application - from a Xamarin Native architecture, to a Xamarin.Forms architecture, because such factors changed over time.
The company in question - a medium-sized B2B software provider - aimed to leverage and extend their core B2B technology platform to a new consumer-facing market. The delivery mechanism was to be a mobile application, and typical of organizations of similar scale, it was investigating cross-platform mobile solutions as an alternative to resourcing independent iOS and Android development projects. With a large existing investment in .NET technologies and an incumbent competitor offering a hybrid/Web-based mobile app, Xamarin was an attractive choice for the company - cross-platform code share on a familiar stack, and the ability to differentiate in the marketplace by providing a performant, native experience to consumers. The decision to go with Xamarin was relatively straightforward, but this quickly led to the next architectural question: Native or Forms? Before proceeding, it's worth spending some time to cover how Xamarin works, and what that question means.
Hello Xamarin (Native)
Xamarin (https://dotnet.microsoft.com/apps/xamarin) is Microsoft's free, open-source framework for cross-platform mobile development. It allows developers to build apps for iOS, Android, macOS, and other devices using the .NET developer platform. The benefits of this include:
- The ability to write code in .NET languages, like C# and F#, rather than in designated platform languages (such as Swift/Objective C for iOS and Java/Kotlin for Android)
- Use of the vast resources within the .NET ecosystem, including the Base Class Library and NuGet package manager
- The ability to share code between mobile platforms, as well as your back-end, using familiar .NET mechanisms
- Access to comprehensive documentation and learning material, as well as an active developer and developer relations community
Xamarin can be compared in intent to other cross-platform mobile frameworks like React Native, Flutter, and Cordova. It differentiates itself from these frameworks by providing full access to the underlying native platforms by default. Using Xamarin.iOS, developers can work directly with beautiful and performant UIKit framework, as well as the rich set of iOS frameworks, such as ARKit, CoreML, SceneKit, and SiriKit. The story is the same on Xamarin.Android. This direct platform access is a core feature of Xamarin, powered by bindings that the team maintains against the iOS, macOS, and Android SDKs. The upshot for developers is, “If you can do it in iOS or Android, you can do it in Xamarin.iOS or Xamarin.Android.”
In addition to being able to access platform-specific frameworks, Xamarin apps have full access to the .NET standard API surface. Given that, cross-platform Xamarin apps typically comprise a combination of platform-specific code for each supported platform, plus the non-platform-specific code that's shared between all projects, as demonstrated on the left of Figure 1. To maximize the code-sharing opportunity in a Xamarin app, it's typical for projects to adopt the Model-View-ViewModel pattern (MVVM). Applying this pattern, the View component of MVVM maps closely to platform-specific UI frameworks, and the View Model and Model components become shared, platform-independent code, as demonstrated on the right side of Figure 1. Libraries such as Xamarin.Essentials (https://docs.microsoft.com/en-us/xamarin/essentials/) make it possible to access service-like platform-specific functionality (e.g., accelerometer, geolocation) from the service layer, without compromising the overall application architecture.
This concept of platform-specific UI code, shared business logic could be considered the original Xamarin development architecture. It's often referred to as Xamarin Traditional or Native, and in this article, I'll use the latter term.
Enter Xamarin.Forms
Following a pattern like MVVM helps “push logic down” to the shared layer of your project. In a Native project, the largest component that isn't shared is, of course, the user interface code. Over time, the Xamarin team began considering how to increase the amount of code-share possible - without compromising the native experience - and Xamarin.Forms was born.
Xamarin.Forms (https://docs.microsoft.com/en-us/xamarin/xamarin-forms/) is a UI toolkit that allows development of native user interfaces in a manner that can be shared across platforms. It introduces an abstraction layer of layouts and controls (think stacks, grids, buttons, labels, etc.) against which a user interface can be defined. At runtime, Xamarin.Forms translates this into a platform-specific interface, comprised of controls native to the running platform, as demonstrated in Figure 2.
By providing a platform-agnostic sharable mechanism for defining user interfaces, Xamarin.Forms greatly reduces the residual amount of unshared code in a cross-platform application. The code sharing diagram from Figure 1 now looks more like Figure 3. Great!
An Easy Decision?
At first glance, taking the Xamarin.Forms route might seem like the obvious answer. More code shared means less code written, right? In practice, it's not that clear cut. The choice between Xamarin Native and Xamarin.Forms comes with tradeoffs, and depending on the circumstances of a given project, one approach may be better suited than the other. There are always exceptions, but I consider the below as good general indicators:
- Team size and composition: Smaller development teams, or teams without dedicated platform specialists, may be better suited to Xamarin.Forms projects. Larger teams with dedicated iOS and Android expertise, having the capacity explore and utilize platform-specific functionality, may better suit a Xamarin Native approach.
- Project Timelines: Xamarin.Forms reduces the amount of time spent writing user interfaces, because you only write them once for use on all platforms. Newer features like Shell can significantly decrease the amount of time needed to set up foundational aspects of app functionality, like navigation and screen hierarchies. If working to time pressure, Xamarin.Forms might be the right fit for a project.
- Design Elements: The Xamarin.Forms API surface is far simpler than that of native platforms. With an API count in the thousands, it's an order of magnitude lower than what you'll see working against the native SDKs directly. The abstraction provided by Xamarin.Forms provides access to the most commonly required controls and properties and will serve most designs. Still, there's no escaping the fact that a significantly reduced API surface also sacrifices some expression. It's possible to extend Xamarin.Forms using features like Custom Renders, Effects, and Platform Specifics, but if a design requires significant customization to the framework, it may be better to spend that time tailoring the experience natively. This point shouldn't be interpreted to mean that good looking interfaces can't be created with Xamarin.Forms. There are many examples of beautiful user interfaces available online, and I've included a few from a friend, Kym Phillpotts, who builds out challenging user interfaces live on Twitch, such as those in Figure 4.
A cross-platform application written in Xamarin is likely to use many similar patterns and libraries, whether following a native approach or being built using Xamarin.Forms. Still, the selection does drive some fundamental architectural decisions, and is generally regarded as an “either/or” decision; based on your project's circumstances, you build a Xamarin Native app or a Xamarin.Forms app. Of course, circumstances aren't static. Over time, the set of factors that pointed toward one decision may change. For long-lived cross-platform apps, an earlier call on Xamarin.Forms or Xamarin Native may start to make less sense.
An App That Started Out Xamarin Native
This premise of changing circumstances isn't theoretical. It's exactly the situation my project's mobile application faced. Now that you know a bit more about Xamarin, I can get back to the story.
Phase 1
Our development team was sold on Xamarin and working through the Native vs. Forms question. Ultimately, the circumstances at the time favored a Native approach:
- A team of several developers, each with specific platform expertise
- A relatively generous timeframe for the initial build
- A consumer-facing target and a desire to differentiate by providing a native experience.
Beyond those factors, there was also resistance to the use of Xamarin.Forms within the organization. Developers were concerned about stability and performance. Senior technology leaders had heard that “Xamarin.Forms is only good for the enterprise market.” The combination of project circumstances and internal perceptions made Xamarin Native the choice for the project, and a native, performant MVVM-based app was developed, with platform-specific user interfaces and shared internals. The project came in on time and no one could fault the decision to take the native route. After a period of iteration and incremental enhancements, the app reached a relative feature-complete state and moved into maintenance. The lower workload meant that resources moved on to new projects or organizations, and eventually, the app was being maintained by a single resource, among other projects.
Phase 2
After chugging along for 18 months with fairly minor work demands, the organization was ready to bring the next phase of functionality to the product. This was a significant roadmap of work and - with increased competitiveness in the market - came with a more aggressive set of timeframes. The resource profile was also looking much lower for the new feature set than for the original build, making the prospect of building out the screens with independent interfaces for both iOS and Android quite daunting. On the other hand, since the original decision to use Native was made, Xamarin.Forms had continued to mature, with improvements in performance, reliability, and features. The combination of these changes essentially reversed the preferred approach, making Xamarin.Forms the more attractive option, as you can see in Figure 5.
What do you do with an app built using Xamarin Native, when the circumstances favor Xamarin.Forms? A rewrite in Forms would be impossible in the required timelines and carried a lot of risk. What was really needed was a way to have the best of both worlds - to keep the existing investment in the native-based app but begin to take advantage of Xamarin.Forms for the new features. As it turned out, that capability exists as something known as Native Forms.
Having Your Cake and Eating It Too: Native Forms
Native Forms (https://docs.microsoft.com/en-us/xamarin/xamarin-forms/platform/native-forms) is a lesser-known feature of Xamarin.Forms that's been available in the framework since version 3.0. It allows developers to easily include Xamarin.Forms ContentPages within a Xamarin Native app, resulting in an app that is part-Native, part-Forms; hence the name. The technique can be used to quickly include a single Xamarin.Forms screen in a native project, or, as was the case in this project, it can be more tightly integrated, in order to create an application with seamless interoperability between the Native and Forms components. Used properly, it's a powerful feature that can help ease an application's transition from Xamarin Native to Xamarin.Forms, or even support the opposite approach: converting a Forms app to Native. For the project in question, Native Forms is exactly what the doctor ordered.
Used properly, Native Forms is a powerful feature that can help ease an application's transition from Xamarin Native to Xamarin.Forms, or vice-versa.
How It Works
Getting started with Native Forms is very straightforward. It works with ordinary Xamarin.Forms ContentPages, which you create in the same manner that you would in a Xamarin.Forms project. From your Native project, you instantiate the Xamarin.Forms page, and call the appropriate Native Forms extension method for your platform. This method returns the native view element representing the Xamarin.Forms page that you can use directly with other native components. For example, given a basic Xamarin.Forms ContentPage like this:
public class MyBasicPage : ContentPage
{
public MyBasicPage()
{
Content = new Label
{
Text = "Hello Native Forms!"
};
}
}
From a native Xamarin.iOS app, you can call the CreateViewController method of an instantiated Xamarin.Forms page, which returns a native UIViewController reference. This can then be added as a child view controller to another non-forms ViewController, or be navigated to, like this:
public void DisplayMyPageOniOS()
{
// instantiate the forms page normally
var page = new MyBasicPage();
// this returns a native view
var vc = page.CreateViewController();
// push like any other UIViewController
NavigationController.PushViewController(vc, true);
}
On Android, it's the same story, but you use the CreateSupportFragment method instead, which returns an instance of a Fragment. This can then be used the same way that any other Fragment is used:
public void DisplayMyPageOnAndroid()
{
// instantiate the forms page normally
var page = new MyBasicPage();
// this returns a native view
var fragment = page.CreateSupportFragment(this);
// use like any other fragment
FragmentManager.BeginTransaction().Replace(Resource.Id.Content, fragment).Commit();
}
On UWP - no surprises - it's again a very similar process. This time, the call is to CreateFrameworkElement:
public void DisplayMyPageOnUWP()
{
// instantiate the forms page normally
var page = new MyBasicPage();
// this returns a native view
var ui = page.CreateFrameworkElement();
// set as frame content
MainFrame.Content = ui;
}
Introducing Xamarin.Forms content into an app in this manner is a very low risk, low commitment option for assessing suitability of the framework. If you're uncertain of whether Xamarin.Forms can handle the requirements of your Xamarin Native application, you don't have to go all in from the beginning. You can test the waters by developing a single new screen using Xamarin.Forms, rather than going through the process of architecting a new project from scratch.
Using Native Forms Effectively
Thanks to the simplicity of Native Forms, Xamarin.Forms content can be included in a Native app with just a few lines of code, and this approach may be suitable if you're just starting out with adding Forms content. For the project in this article, the aim was to produce a deeper integration between the two worlds. The guiding principle was that Xamarin.Forms should be a first-class citizen in the app; the idea was that the experience of working with Forms components should be as similar as possible to that of the Native components, both for the developer and the end user. A few tips helped along the way.
Tip 1: Start by Recreating an Existing Screen
Recreating an existing native screen in Xamarin.Forms is probably the single most effective way to prepare for integrating Xamarin.Forms into a Xamarin Native project. The process will give you an understanding of parts of your existing design that are easy to reproduce using Xamarin.Forms, and more importantly, those that may require additional customization to implement. Your approach to layouts may need to change, as native platforms tend to favor constraint or relative positioning systems. In contrast, Xamarin.Forms typically avoids these, in favor of grid-based layouts. Services and helper libraries being used in the view layer may also need to be reproduced in Xamarin.Forms. For example, a theme service that has been implemented in the iOS and Android projects will also need to be implemented (or wrapped) in order to be used with Xamarin.Forms content.
Recreating an existing native screen in Xamarin.Forms is probably the single most effective way to prepare for integrating Xamarin.Forms into a native project.
Choice of screen is important too. It's tempting to pick one of the “easy” screens, but a screen of mid-to-high complexity that better represents the UI patterns used in your app will smoke out more of the areas that need additional support earlier. Identifying and addressing these in the beginning reduces the friction of using Forms in the project and helps ensure that subsequent development work isn't interrupted by the need to deal with issues related to bridging the forms and native paradigms.
Tip 2: Take Advantage of Deferred Initialization
All projects using Xamarin.Forms - whether “full-Forms” or using it via Native Forms - must call Forms.Init() to initialize the framework. The call only needs to be made once, but it must happen before any Forms-related classes are used. In a typical Xamarin.Forms project, that means it's made during startup, as Forms is needed immediately. In a Native Forms project, Forms.Init() still needs to be called, but not until the first Xamarin.Forms ContentPage needs to be displayed. If your app doesn't immediately display a Forms-based page, there's no need to perform the initialization at startup. Deferring initialization helps keep your app's startup time low and ensures that the initialization work is only performed when it's actually needed. If your users only visit a Xamarin.Forms-based screen once every five sessions, why have them pay the cost of initialization during the other four?
In a Native Forms project, deferring initialization helps keep your app's startup time low and ensures that work is only performed when it's actually needed.
Tip 3: Make Navigation Between Native and Forms Screens Seamless
In the early stages of a progressive migration from Xamarin Native to Xamarin.Forms, it's likely that the interoperability between the two worlds will be unidirectional: navigating from a Xamarin Native context to Xamarin.Forms context. As the proportion of native and forms content becomes more balanced, standardizing and centralizing the interoperability between the two worlds ensures that the arrangement is maintainable.
An ideal place to contain Native Forms interop is a navigation service. In the MVVM world, a navigation service allows movement between screens of your application to be invoked from the ViewModel layer, abstracted from the specifics of Views that are involved. This is useful in a Native Forms context, because being able to move between the two classes of screens without requiring ViewModels to check for and handle the transitions is desirable. Encoding the knowledge of whether target screens are Forms-based or native into each ViewModel would be brittle and prone to breakage, as more screens are created in or converted to Xamarin.Forms.
By now, a few of the popular Xamarin MVVM frameworks with navigators support native/forms navigation out of the box, but that hasn't always been the case. Not all MVVM frameworks include a navigator, so it's fairly common to see custom implementations. Adding support for Xamarin.Forms to an existing custom navigation service can be simpler than it sounds. Consider the code snippet below, an excerpt from an Android-based navigation service that originally only handled native Fragment types and has been extended to support Xamarin.Forms. When the service sees that a ContentPage has been registered against a ViewModel, it calls CreateSupportFragment on the page and returns the resulting Fragment. In this way, the service transparently supports Forms-based screens and keeps the API stable.
Fragment ViewForVM(object vm, Type viewType)
{
if (viewType.IsSubclassOf(typeof(Fragment)))
return Create<Fragment>(viewType, vm);
if (viewType.IsSubclassOf(typeof(ContentPage)))
{
InitFormsIfNeeded();
var p = Create<ContentPage>(viewType, vm);
return p.CreateSupportFragment(Context);
}
/* handle unrecognized types here */
}
Lines 3 and 4 of that snippet handle the original, native-only case, in which navigation occurs to a Fragment. Adding lines 6 to 12 extends the method to seamlessly support the handling of Forms-based content pages. Because interop between Native and Forms is handled by the navigation service, calling code (such as a ViewModel requesting navigation to another screen) remains blissfully unaware. An additional benefit of this approach is the call at line 8 to initialize Xamarin.Forms. Centralized handling of Xamarin.Forms navigation makes it easy to support deferred initialization without modifying callers.
Low Risk, High Reward
The focus on a tight integration between the Native and Forms worlds helped deliver significant benefits to the app in question, with few drawbacks.
Leveraging the Existing Investment
- Native Forms made it possible to maintain and leverage the existing investment of work in a native mobile app, while also allowing new features to be developed using the Xamarin.Forms framework. The ability to mix-and-match Native and Forms content greatly de-risked the introduction of Xamarin.Forms into the project, and use of MVVM for both the Native and Forms components made it possible to reuse work if falling back, or when falling forward (converting existing Native screens to Forms). Maintaining the native shell meant that it was always possible to fall back to developing native screens if needed (it wasn't).
Native Forms made it possible to maintain and leverage the existing investment in a native mobile app, while allowing new features to be developed using the Xamarin.Forms framework.
Improved Delivery Capacity
The introduction of Xamarin.Forms yielded productivity improvements beyond just those related to sharing more code. Developing individual user interfaces for each platform carried the overhead of co-ordination between two resources (the iOS developer and the Android developer) and a shared dependency (the ViewModel), which might be developed by either, or another resource entirely. Once developing with Xamarin.Forms, it was more reasonable and efficient to have a single resource responsible for an entire slice of functionality, from the user interface down to the ViewModel and service layers. This reduction in co-ordination overhead and interdependencies improved both productivity and quality.
Perhaps surprisingly, it turned out that many interfaces can be easier to create in Xamarin.Forms than they are using native platform paradigms. Layouts provided by Xamarin.Forms - particularly the trusty Grid - are a delight to work with. Although it might seem constraining to define an interface in terms of a grid, it provides a very expressive layout API. It can be bent to your will with a few techniques, making many designs quicker and easier to create, and more importantly, easier to modify than in a (native-style) constraint-based approach.
Perhaps surprisingly, it turned out that many interfaces can be easier to create in Xamarin.Forms than they are using native platform paradigms.
Finally - of course - the improved productivity of increased code share cannot be discounted. Coming from a Xamarin Native background, there's a certain satisfaction that you get after opening a screen on Android that was developed using Xamarin.Forms on iOS and seeing that it's ready to go with little or no customization. This was helped by the effort spent up front making sure the appropriate Xamarin.Forms customizations were in place after the first native screen rewrite.
Access to New Features
The features of Xamarin.iOS and Xamarin.Android are largely driven by the underlying platforms. Xamarin.Forms - as a product operated by the Xamarin team itself - evolves specifically to meet the needs of .NET developers writing Xamarin apps. For example, Hot Reload - a feature that improves inner loop productivity by allowing parts of an application to be modified while it's still running - isn't broadly available on iOS or Android. However, the Xamarin.Forms team recently released a preview of Hot Reload for Xamarin.Forms, which allows changes to XAML-based user interfaces to be displayed in real time, without recompilation. By incorporating the latest version of Xamarin.Forms in the project, it's now possible to make use of the newest cutting edge features that the team is adding to the product.
In a similar vein, but for a different (and more technical) set of reasons, introducing Xamarin.Forms into the project has made it possible to support code push style functionality with our internal testers. Ordinarily, test versions of a mobile application go through a relatively lengthy build and distribution process before being released to testers. By implementing a code push system, it's possible to patch a deployed app with changes or additions to functionality, without producing an entire new build. Xamarin.Forms facilitates code push by removing the need to introduce native types dynamically (as Xamarin.Forms pages all ultimately derive from System.Object, a .NET type). In addition, the new Mono interpreter allows Xamarin.iOS apps to dynamically load and execute (read: interpret) .NET assemblies that weren't included in the original app bundle - a possibility not offered in the traditional Ahead-of-Time (AOT) execution mode. Making a small tweak to a screen in response to business feedback and having it immediately available on their device is a great way to impress and give an impression of technical sophistication.
Not Without Drawbacks
I've talked mostly about benefits but there are a few caveats to a Native Forms approach that're worth considering. Adding Xamarin.Forms to a native project is in some ways similar to adding support for an entirely new platform. Building new screens using Forms requires predominantly Xamarin.Forms experience, but maintaining the app still requires the deeper level of platform expertise that a Xamarin Native app demands. This has implications for team resourcing but can be managed; even when working primarily with Xamarin.Forms, it's always beneficial to have a solid understanding of the underlying platforms.
Another implication of an “additional platform” is the added overhead involved in adding or modifying application-wide functionality. For example, the new iOS13 and Android 10 versions of their respective platforms each introduce a new Dark Mode feature. A Xamarin Native app targeting iOS and Android needs work on each platform to adopt the new functionality correctly. If the app also uses Native Forms, adopting the feature correctly in the Forms-based components is more work on top of this.
For the project in question, the benefits outweighed the drawbacks, but that's a decision that should be made on a case-by-case basis.
An App that Ended up Forms?
The introduction of Xamarin.Forms helped the project deliver the second phase of functionality more quickly and with less resourcing than it would have taken had the team stuck with the Xamarin Native approach. Native Forms made that possible, allowing new Forms content to co-exist with - and leverage - the existing work. The second phase bumped the screen count of the app by about 30%, with the entire set of new screens all written in Xamarin.Forms. With the touch an existing screen; convert an existing screen policy, several additional native interfaces from the first phase of work also ended up with a Forms rewrite. The balance came in at around 60/40 Native vs. Forms, so saying that the app ended up as a Forms app wouldn't be entirely accurate. That said, were there to be a hypothetical Phase 3, who knows? Maybe it would have ended up a Xamarin.Forms app.