December 8th, 2017

Native Forms is Polished and Shiny in Xamarin.Forms 2.5.0

E.Z. Hart
Senior Software Engineer

Since we previewed Native Forms in June, we’ve been working to smooth the rough edges and polish it up for Xamarin.Forms 2.5.0 by fixing bugs and integrating the excellent feedback from the community.

Last month at Microsoft Connect(); in New York, several Native Forms examples were used in the keynote presentation. Most notably, James Montemagno demonstrated adding a Xamarin.Forms page to the open source Kickstarter iOS application. You can watch the entire keynote on Channel9.

Another example is the SmartHotel360 suite of applications, which includes a maintenance application that uses Native Forms to composite Xamarin.Forms pages into native Xamarin.iOS and Xamarin.Android applications. The full code for these applications will be made available soon on GitHub. <!– –>

This post covers these changes and serves as a guide for you to get started embedding Native Forms in your own applications.

What is Native Forms?

The tl;dr; is that Native Forms allows you to use a Xamarin.Forms ContentPage in a native Xamarin.iOS, Xamarin.Android, or UWP application.

But why, you may ask, would I want to do that? Let’s consider some scenarios:

Existing Xamarin.Forms Reuse

Perhaps you wrote a login page for a service in a previous Xamarin.Forms project. Now, you’re working on a native Xamarin project which uses the same service. Rather than write that page again, you can simply reuse it directly from the Forms project.

Migrate Xamarin.Forms to Xamarin Native

Let’s say you used Xamarin.Forms to quickly create a prototype or proof-of-concept application. For version two, you have decided that on iOS you need to go native. However, you’re not thrilled with the prospect of rewriting every single page from the prototype for your native app. With Native Forms, you can reuse pages from the prototype and only rewrite what you absolutely have to.

Mix Xamarin.Forms into Xamarin Native Projects

You’re working on multiple native projects and facing the prospect of writing the same “Settings” and “About” pages three times. Stop! Just write them once in a Xamarin.Forms project and embed them in your native apps!

A Simple Scenario

To show Native Forms in action, let’s walk through a scenario with some code. The full code for this example can be found on GitHub.

Let’s say I’m creating brand new native applications for UWP, Android, and iOS. In each one, I’d like to add a tip calculator, but I really don’t want to write three tip calculators from scratch. Luckily for me, Charles Petzold has already written one in Xamarin.Forms, so all I have to do is embed it in each of the native apps.

In the demo solution, I’ve created three native projects, plus a project for the TipCalc code:

Solution
Solution

In my TipCalc project, I’ve simply copy-pasted the code from the original TipCalc solution’s PCL project. The code works as-is without any changes, but in my project I’ve updated some obsolete XAML and added some MessagingCenter code, which I’ll talk about later.

To embed the code in each native application, I need to add a reference to the TipCalc project and install the Xamarin.Forms NuGet package. Once I’ve done that, my references in each native project will look something like this:

iOS project references
iOS project references

Before each native project can embed any Xamarin.Forms pages, they need to initialize Forms by calling Forms.Init(). When a native application does this is up to you, as long as it happens before constructing any instance of a Forms ContentPage. You may choose to do it at startup (e.g., during UIApplicationDelegate.FinishedLaunching() or Activity.OnCreate()), or you may want to wait until right before the first ContentPage is constructed. The right option for you will depend primarily on when it’s most convenient in your application flow.

After Xamarin.Forms is initialized, it’s just a matter of setting up navigation to the TipCalcPage. Native Forms adds some extension methods to Xamarin.Forms, which handle converting a ContentPage to the appropriate native type.

In a native iOS project, for example, you would normally use UINavigationController.PushViewController() with a UIViewController. Native Forms provides an extension to ContentPage, which converts the page to a UIViewContoller for you. The method for navigating to the tip calculator could be as simple as:

public void NavigateToTipCalc()
{
    _navigation.PushViewController(new TipCalcPage().CreateViewController(), true);
}

where _navigation is a UINavigationController. That’s it; call that method from the “Tip Calculator” button and the application navigates to the Xamarin.Forms page.

Navigating to the embedded Forms page on iOS
Navigating to the embedded Forms page on iOS

On Android, Native Forms provides the CreateFragment() and CreateSupportFragment() methods. These will convert a ContentPage into a Fragment suitable for navigation. For example, add a method like this to a FragmentActivity:

public void NavigateToTipCalc()
{
    var ft = SupportFragmentManager.BeginTransaction();

    ft.AddToBackStack(null);
    ft.Replace(Resource.Id.fragment_frame_layout,
        new TipCalcPage().CreateSupportFragment(this), "TipCalc");

    ft.Commit();
}
Navigating to the embedded Forms page on Android
Navigating to the embedded Forms page on Android

For native UWP applications, navigation is typically done with the Frame class; the Frame.Navigate() method takes a Page type and navigates to an instance of it. Native Forms provides a similar extension to Frame, which takes a ContentPage instance. Navigating can be as simple as:

private void NavigateToTipCalc()
{
    Frame.Navigate(new TipCalcPage());
}
Navigating to the embedded Forms page on UWP
Navigating to the embedded Forms page on UWP

As you can see, using Native Forms doesn’t take a whole lot of code.

Handling Communication

Chances are good that you also need to communicate between the native context and the Xamarin.Forms portions of your apps. If you’re reusing Xamarin.Forms code from another project, you may already have something set up using whatever event aggregator, message bus, or other method makes sense for your stack. But if you don’t already have something in place, Xamarin.Forms comes with a built-in messaging service (called MessagingCenter) which can be used to wire up communications between native applications and the Xamarin.Forms pages they host.

In this demo solution, I’ve modified the TipCalc project a bit to allow it to send and receive data to and from its host app using MessagingCenter. I’ve defined messages and their arguments:

public static class Messages {
    public static object Sender = new object();
    public const string InitialAmount = "InitialAmount";
    public const string Tip = "Tip";
}

public class InitialAmountArgs {
    public InitialAmountArgs(double initialAmount) {
        InitialAmount = initialAmount;
    }

    public double InitialAmount { get; }
}

public class TipArgs {
    public TipArgs(double tip) {
        Tip = tip;
    }

    public double Tip { get; }
}

With that infrastructure in place, it’s easy for the native applications to specify an initial amount for the tip calculator. Each native app has a field for entering the initial amount; when the user updates that field, a message is sent:

MessagingCenter.Send(TipCalc.Messages.Sender, TipCalc.Messages.InitialAmount, 
    new InitialAmountArgs(InitialAmount));

The view model in the TipCalc project (TipCalcModel) listens for that message and reacts accordingly:

MessagingCenter.Subscribe(this, Messages.InitialAmount, (s, e) =>
{
    SubTotal = e.InitialAmount;
});

It’s also simple for the native apps to respond to changes in the calculated tip value. Whenever the TipAmount in TipCalcModel changes, it sends a message:

MessagingCenter.Send(Messages.Sender, Messages.Tip, new TipArgs(value));

The native application can listen for that message and react; for example, the iOS application sets the text of a UILabel:

MessagingCenter.Subscribe(TipCalc.Messages.Sender, TipCalc.Messages.Tip,
    (obj, args) => TipAmount.Text = args.Tip.ToString("C"));

Here’s the app in action on UWP. The user enters an initial amount, which is passed to the tip calculator, and the result from the tip calculator is relayed back to the native page:

Communication on UWP
Communication on UWP

Just Pages?

Savvy readers will have noticed that the extension methods all provide return types that don’t necessarily require full page navigation. It’s also possible to use UIViewController, FrameworkElement, and Fragment as part of a page rather than a full screen. We previously showed how to display a ContentPage on UWP inside of a Flyout, instead of a full page.

In theory, these methods could be used to embed a ContentPage as part of a larger page on each platform. As of right now, that’s very much an officially unsupported use case. That said, if you experiment with that use case, I’d love to hear how it works for you.

Update to 2.5.0 Today!

You can begin using Native Forms today in our latest stable version, Xamarin.Forms 2.5.0, available on NuGet. If you have any feedback or suggestions, please let us know in the forums!

Author

E.Z. Hart
Senior Software Engineer

I've been working on Xamarin.Forms since 2015, and .NET MAUI since 2020.

0 comments

Discussion are closed.