I have been meaning to make a blog post for a while about how we have been using Forms Embedding for the last year on our project at work. After I heard about the ability to embed Xamarin.Forms views into Xamarin Native projects, I was thrilled. I made a post about it a while back when we had barely started using it. A year in I’d like to summarize our experiences with it.
TL;DR: Works great, but needs some adjustments to make navigation work for all intended purposes.
So much time saved!
As you might know, Xamarin.Forms lets you write one codebase and use it on multiple platforms. That means write a view/page once. When we started using Embedding in our project, this was for a page that required multiple iterations to get the interaction and the validation just right to fit our users’ needs. Traditionally this had meant fixing the things on both platforms (Android and iOS) and making sure that it was implemented the same way. Normally, iterating at such a fast pace, some things would get lost or forgotten in the moment, leading one platform to not having implemented a specific tweak. This wasn’t a problem anymore by using the Embedding practice.
Navigation hacks
By using Forms Embedding, you have to provide the navigation natively – that means you aren’t able to use things like Navigation.PushAsync(…), but you need to replace the fragment on Android and use the UINavigationController on iOS to use the Forms view. We solved this by using dependency injection and utilizing an INavigationService that would perform the navigation natively for each platform. The interface had the following methods:
public interface INavigationService
{
void Push(INavigationEnabledPage page);
void Pop();
}
The INavigationEnabledPage would be of type ContentPage. This worked fine in simple scenarios, like navigating from a native page to a Forms page, but we had to work out some kinks to make all scenarios work., For example navigating the other way around (Forms page ➡ Native page).
The other thing about having to provide the navigation natively is that you also have to do the navigation bar customization natively. We solved this by expanding our INavigationService. At first, we expanded the Push-method:
void Push(INavigationEnabledPage page, string title);
The “title” parameter would set the navigation bar title for the implemented platform. But what if we wanted to add a button to the navigation bar, or a clickable text? We had to extended the INavigationService with a couple of more methods:
void PushWithNavigationBarText(INavigationEnabledPage page, string title, string buttonText, EventHandler eventHandler);
void PushWithNavigationBarIcon(INavigationEnabledPage page, string title, string iconFileName, EventHandler eventHandler);
The first method would add clickable text to the right of the navigation bar with an event handler for when the text was clicked, and the second method would do the same but for an image instead of text.
Since a Forms view gets rendered to a (Support)Fragment on Android and a UIViewController on iOS, the implementations of these methods were a bit different. A fragment needs a supporting activity in order to be presented, whereas a ViewController can be presented by itself. This meant that in most cased, a supporting Activity had to be created for Android and the implementation of INavigationService would set the activity’s fragment to that of the Forms page. For iOS we would simply push the Forms page onto the iOS navigation stack.
TabbedPage
An interesting discovery we made about embedding is that you can embed not only a ContentPage, but also a TabbedPage! Well… almost. You can do it on iOS. Probably because of the afforementioned fact that a UIViewController is self-contained. We would have loved to see this being able on Android too. Either way, we solved this on Android by creating a native Android tabbed page and embedding the Forms views as tabs.
To OnAppear or not to OnAppear
Since we had to deal with the navigation natively, this also led to some frustrating moments at times. The method for a Forms page OnAppearing() would sometimes behave differently on each platform. Specifically when using tabbed pages. We managed to track down one of these issues, which was that when using an Android tabbed page with top tabs, the OnAppearing()-methods would only fire once. I’m guessing this has something to do with the fact that top tabbed Android pages loads all the tabbed pages only once. There was also an issue on iOS. When you want to present a view controller modally on iOS, you use the UINavigationController-method PresentViewController(). When this was dismissed, an underlying Forms-page would fire of its OnAppearing()-method. This meant that the Forms view would get focus and pop the entire navigation stack. Quite frustrating. We ended up replacing our usages of PresentViewController() with PushViewController() to battle this. A small price to pay, but a bit annoying.
Wrapping up
We have saved so much time by choosing to only create new functionality now using Forms Embedding in our application. We’re able to use the Forms Previewer, Hot Reload and great documentation about Xamarin.Forms. Also, we’re able to use all the great plugins created by the awesome Xamarin community. This year has given us time to reap the rewards of finally being able to use Xamarin.Forms, but has also learnt us the “gotchas” involving this type of embedding. For anyone else who are also working on a Xamarin native codebase, I would highly recommend utilizing this embedding technique, maybe mostly so that you never have to implement a UITableView ever again. Check out the official documentation about this if you want to learn more, or please contact me if you want to know more details about our implementation and utilization of this.