Xamarin.Forms in Xamarin Native Projects
Typically, a Xamarin.Forms application includes one or more pages that derive from ContentPage
, and these pages are shared by all platforms in a .NET Standard library project or Shared Project. However, Native Forms enables ContentPage
-derived pages to be added directly to native Xamarin.iOS, Xamarin.Android, and UWP applications. Compared to having the native project consume ContentPage
-derived pages from a .NET Standard library project or Shared Project, the advantage of adding pages directly to native projects is that the pages can be extended with native views. Native views can then be named in XAML with x:Name
and referenced from the code-behind. For more information about native views, see Native Views.
The process for consuming a Xamarin.Forms ContentPage
-derived page in a native project is as follows:
- Add the Xamarin.Forms NuGet package to the native project.
- Add the
ContentPage
-derived page, and any dependencies, to the native project. - Call the
Forms.Init
method. - Construct an instance of the
ContentPage
-derived page and convert it to the appropriate native type using one of the following extension methods:CreateViewController
for iOS,CreateSupportFragment
for Android, orCreateFrameworkElement
for UWP. - Navigate to the native type representation of the
ContentPage
-derived page using the native navigation API.
Xamarin.Forms must be initialized by calling the Forms.Init
method before a native project can construct a ContentPage
-derived page. Choosing when to do this primarily depends on when it's most convenient in your application flow – it could be performed at application startup, or just before the ContentPage
-derived page is constructed. In this article, and the accompanying sample applications, the Forms.Init
method is called at application startup.
Note
The NativeForms sample application solution does not contain any Xamarin.Forms projects. Instead, it consists of a Xamarin.iOS project, a Xamarin.Android project, and a UWP project. Each project is a native project that uses Native Forms to consume ContentPage
-derived pages. However, there's no reason why the native projects couldn't consume ContentPage
-derived pages from a .NET Standard library project or Shared Project.
When using Native Forms, Xamarin.Forms features such as DependencyService
, MessagingCenter
, and the data binding engine, all still work. However, page navigation must be performed using the native navigation API.
iOS
On iOS, the FinishedLaunching
override in the AppDelegate
class is typically the place to perform application startup related tasks. It's called after the application has launched, and is usually overridden to configure the main window and view controller. The following code example shows the AppDelegate
class in the sample application:
[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public static AppDelegate Instance;
UIWindow _window;
AppNavigationController _navigation;
public static string FolderPath { get; private set; }
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
Forms.Init();
// Create app-level resource dictionary.
Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
Xamarin.Forms.Application.Current.Resources = new MyDictionary();
Instance = this;
_window = new UIWindow(UIScreen.MainScreen.Bounds);
UINavigationBar.Appearance.SetTitleTextAttributes(new UITextAttributes
{
TextColor = UIColor.Black
});
FolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
NotesPage notesPage = new NotesPage()
{
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
UIViewController notesPageController = notesPage.CreateViewController();
notesPageController.Title = "Notes";
_navigation = new AppNavigationController(notesPageController);
_window.RootViewController = _navigation;
_window.MakeKeyAndVisible();
notesPage.Parent = null;
return true;
}
// ...
}
The FinishedLaunching
method performs the following tasks:
- Xamarin.Forms is initialized by calling the
Forms.Init
method. - A new
Xamarin.Forms.Application
is object is created, and its application-level resource dictionary is set to aResourceDictionary
that's defined in XAML. - A reference to the
AppDelegate
class is stored in thestatic
Instance
field. This is to provide a mechanism for other classes to call methods defined in theAppDelegate
class. - The
UIWindow
, which is the main container for views in native iOS applications, is created. - The
FolderPath
property is initialized to a path on the device where note data will be stored. - A
NotesPage
object is created, which is a Xamarin.FormsContentPage
-derived page defined in XAML, and its parent is set to the previously createdXamarin.Forms.Application
object. - The
NotesPage
object is converted to aUIViewController
using theCreateViewController
extension method. - The
Title
property of theUIViewController
is set, which will be displayed on theUINavigationBar
. - A
AppNavigationController
is created for managing hierarchical navigation. This is a custom navigation controller class, which derives fromUINavigationController
. TheAppNavigationController
object manages a stack of view controllers, and theUIViewController
passed into the constructor will be presented initially when theAppNavigationController
is loaded. - The
AppNavigationController
object is set as the top-levelUIViewController
for theUIWindow
, and theUIWindow
is set as the key window for the application and is made visible. - The
Parent
property of theNotesPage
object is set tonull
, to prevent a memory leak.
Once the FinishedLaunching
method has executed, the UI defined in the Xamarin.Forms NotesPage
class will be displayed, as shown in the following screenshot:
Important
All ContentPage
-derived pages can consume resources defined in the application-level ResourceDictionary
, provided that the Parent
property of the page is set to the Application
object.
Interacting with the UI, for example by tapping on the + Button
, will result in the following event handler in the NotesPage
code-behind executing:
void OnNoteAddedClicked(object sender, EventArgs e)
{
AppDelegate.Instance.NavigateToNoteEntryPage(new Note());
}
The static
AppDelegate.Instance
field enables the AppDelegate.NavigateToNoteEntryPage
method to be invoked, which is shown in the following code example:
public void NavigateToNoteEntryPage(Note note)
{
NoteEntryPage noteEntryPage = new NoteEntryPage
{
BindingContext = note,
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
var noteEntryViewController = noteEntryPage.CreateViewController();
noteEntryViewController.Title = "Note Entry";
_navigation.PushViewController(noteEntryViewController, true);
noteEntryPage.Parent = null;
}
The NavigateToNoteEntryPage
method converts the Xamarin.Forms ContentPage
-derived page to a UIViewController
with the CreateViewController
extension method, and sets the Title
property of the UIViewController
. The UIViewController
is then pushed onto AppNavigationController
by the PushViewController
method. Therefore, the UI defined in the Xamarin.Forms NoteEntryPage
class will be displayed, as shown in the following screenshot:
When the NoteEntryPage
is displayed, back navigation will pop the UIViewController
for the NoteEntryPage
class from the AppNavigationController
, returning the user to the UIViewController
for the NotesPage
class. However, popping a UIViewController
from the iOS native navigation stack does not automatically dispose of the UIViewController
and attached Page
object. Therefore, the AppNavigationController
class overrides the PopViewController
method, to dispose of view controllers on backwards navigation:
public class AppNavigationController : UINavigationController
{
//...
public override UIViewController PopViewController(bool animated)
{
UIViewController topView = TopViewController;
if (topView != null)
{
// Dispose of ViewController on back navigation.
topView.Dispose();
}
return base.PopViewController(animated);
}
}
The PopViewController
override calls the Dispose
method on the UIViewController
object that's been popped from the iOS native navigation stack. Failure to do this will result in the UIViewController
and attached Page
object being orphaned.
Important
Orphaned objects can't be garbage collected, and so result in a memory leak.
Android
On Android, the OnCreate
override in the MainActivity
class is typically the place to perform application startup related tasks. The following code example shows the MainActivity
class in the sample application:
public class MainActivity : AppCompatActivity
{
public static string FolderPath { get; private set; }
public static MainActivity Instance;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Forms.Init(this, bundle);
// Create app-level resource dictionary.
Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
Xamarin.Forms.Application.Current.Resources = new MyDictionary();
Instance = this;
SetContentView(Resource.Layout.Main);
var toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
SupportActionBar.Title = "Notes";
FolderPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));
NotesPage notesPage = new NotesPage()
{
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
AndroidX.Fragment.App.Fragment notesPageFragment = notesPage.CreateSupportFragment(this);
SupportFragmentManager
.BeginTransaction()
.Replace(Resource.Id.fragment_frame_layout, mainPage)
.Commit();
//...
notesPage.Parent = null;
}
...
}
The OnCreate
method performs the following tasks:
- Xamarin.Forms is initialized by calling the
Forms.Init
method. - A new
Xamarin.Forms.Application
is object is created, and its application-level resource dictionary is set to aResourceDictionary
that's defined in XAML. - A reference to the
MainActivity
class is stored in thestatic
Instance
field. This is to provide a mechanism for other classes to call methods defined in theMainActivity
class. - The
Activity
content is set from a layout resource. In the sample application, the layout consists of aLinearLayout
that contains aToolbar
, and aFrameLayout
to act as a fragment container. - The
Toolbar
is retrieved and set as the action bar for theActivity
, and the action bar title is set. - The
FolderPath
property is initialized to a path on the device where note data will be stored. - A
NotesPage
object is created, which is a Xamarin.FormsContentPage
-derived page defined in XAML, and its parent is set to the previously createdXamarin.Forms.Application
object. - The
NotesPage
object is converted to aFragment
using theCreateSupportFragment
extension method. - The
SupportFragmentManager
class creates and commits a transaction that replaces theFrameLayout
instance with theFragment
for theNotesPage
class. - The
Parent
property of theNotesPage
object is set tonull
, to prevent a memory leak.
For more information about Fragments, see Fragments.
Once the OnCreate
method has executed, the UI defined in the Xamarin.Forms NotesPage
class will be displayed, as shown in the following screenshot:
Important
All ContentPage
-derived pages can consume resources defined in the application-level ResourceDictionary
, provided that the Parent
property of the page is set to the Application
object.
Interacting with the UI, for example by tapping on the + Button
, will result in the following event handler in the NotesPage
code-behind executing:
void OnNoteAddedClicked(object sender, EventArgs e)
{
MainActivity.Instance.NavigateToNoteEntryPage(new Note());
}
The static
MainActivity.Instance
field enables the MainActivity.NavigateToNoteEntryPage
method to be invoked, which is shown in the following code example:
public void NavigateToNoteEntryPage(Note note)
{
NoteEntryPage noteEntryPage = new NoteEntryPage
{
BindingContext = note,
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
AndroidX.Fragment.App.Fragment noteEntryFragment = noteEntryPage.CreateSupportFragment(this);
SupportFragmentManager
.BeginTransaction()
.AddToBackStack(null)
.Replace(Resource.Id.fragment_frame_layout, noteEntryFragment)
.Commit();
noteEntryPage.Parent = null;
}
The NavigateToNoteEntryPage
method converts the Xamarin.Forms ContentPage
-derived page to a Fragment
with the CreateSupportFragment
extension method, and adds the Fragment
to the fragment back stack. Therefore, the UI defined in the Xamarin.Forms NoteEntryPage
will be displayed, as shown in the following screenshot:
When the NoteEntryPage
is displayed, tapping the back arrow will pop the Fragment
for the NoteEntryPage
from the fragment back stack, returning the user to the Fragment
for the NotesPage
class.
Enable back navigation support
The SupportFragmentManager
class has a BackStackChanged
event that fires whenever the content of the fragment back stack changes. The OnCreate
method in the MainActivity
class contains an anonymous event handler for this event:
SupportFragmentManager.BackStackChanged += (sender, e) =>
{
bool hasBack = SupportFragmentManager.BackStackEntryCount > 0;
SupportActionBar.SetHomeButtonEnabled(hasBack);
SupportActionBar.SetDisplayHomeAsUpEnabled(hasBack);
SupportActionBar.Title = hasBack ? "Note Entry" : "Notes";
};
This event handler displays a back button on the action bar provided that there's one or more Fragment
instances on the fragment back stack. The response to tapping the back button is handled by the OnOptionsItemSelected
override:
public override bool OnOptionsItemSelected(Android.Views.IMenuItem item)
{
if (item.ItemId == global::Android.Resource.Id.Home && SupportFragmentManager.BackStackEntryCount > 0)
{
SupportFragmentManager.PopBackStack();
return true;
}
return base.OnOptionsItemSelected(item);
}
The OnOptionsItemSelected
override is called whenever an item in the options menu is selected. This implementation pops the current fragment from the fragment back stack, provided that the back button has been selected and there are one or more Fragment
instances on the fragment back stack.
Multiple activities
When an application is composed of multiple activities, ContentPage
-derived pages can be embedded into each of the activities. In this scenario, the Forms.Init
method need be called only in the OnCreate
override of the first Activity
that embeds a Xamarin.Forms ContentPage
. However, this has the following impact:
- The value of
Xamarin.Forms.Color.Accent
will be taken from theActivity
that called theForms.Init
method. - The value of
Xamarin.Forms.Application.Current
will be associated with theActivity
that called theForms.Init
method.
Choose a file
When embedding a ContentPage
-derived page that uses a WebView
that needs to support an HTML "Choose File" button, the Activity
will need to override the OnActivityResult
method:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResultCallbackRegistry.InvokeCallback(requestCode, resultCode, data);
}
UWP
On UWP, the native App
class is typically the place to perform application startup related tasks. Xamarin.Forms is usually initialized, in Xamarin.Forms UWP applications, in the OnLaunched
override in the native App
class, to pass the LaunchActivatedEventArgs
argument to the Forms.Init
method. For this reason, native UWP applications that consume a Xamarin.Forms ContentPage
-derived page can most easily call the Forms.Init
method from the App.OnLaunched
method:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
// ...
Xamarin.Forms.Forms.Init(e);
// Create app-level resource dictionary.
Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
Xamarin.Forms.Application.Current.Resources = new MyDictionary();
// ...
}
In addition, the OnLaunched
method can also create any application-level resource dictionary that's required by the application.
By default, the native App
class launches the MainPage
class as the first page of the application. The following code example shows the MainPage
class in the sample application:
public sealed partial class MainPage : Page
{
NotesPage notesPage;
NoteEntryPage noteEntryPage;
public static MainPage Instance;
public static string FolderPath { get; private set; }
public MainPage()
{
this.NavigationCacheMode = NavigationCacheMode.Enabled;
Instance = this;
FolderPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));
notesPage = new Notes.UWP.Views.NotesPage
{
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
this.Content = notesPage.CreateFrameworkElement();
// ...
notesPage.Parent = null;
}
// ...
}
The MainPage
constructor performs the following tasks:
- Caching is enabled for the page, so that a new
MainPage
isn't constructed when a user navigates back to the page. - A reference to the
MainPage
class is stored in thestatic
Instance
field. This is to provide a mechanism for other classes to call methods defined in theMainPage
class. - The
FolderPath
property is initialized to a path on the device where note data will be stored. - A
NotesPage
object is created, which is a Xamarin.FormsContentPage
-derived page defined in XAML, and its parent is set to the previously createdXamarin.Forms.Application
object. - The
NotesPage
object is converted to aFrameworkElement
using theCreateFrameworkElement
extension method, and then set as the content of theMainPage
class. - The
Parent
property of theNotesPage
object is set tonull
, to prevent a memory leak.
Once the MainPage
constructor has executed, the UI defined in the Xamarin.Forms NotesPage
class will be displayed, as shown in the following screenshot:
Important
All ContentPage
-derived pages can consume resources defined in the application-level ResourceDictionary
, provided that the Parent
property of the page is set to the Application
object.
Interacting with the UI, for example by tapping on the + Button
, will result in the following event handler in the NotesPage
code-behind executing:
void OnNoteAddedClicked(object sender, EventArgs e)
{
MainPage.Instance.NavigateToNoteEntryPage(new Note());
}
The static
MainPage.Instance
field enables the MainPage.NavigateToNoteEntryPage
method to be invoked, which is shown in the following code example:
public void NavigateToNoteEntryPage(Note note)
{
noteEntryPage = new Notes.UWP.Views.NoteEntryPage
{
BindingContext = note,
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
this.Frame.Navigate(noteEntryPage);
noteEntryPage.Parent = null;
}
Navigation in UWP is typically performed with the Frame.Navigate
method, which takes a Page
argument. Xamarin.Forms defines a Frame.Navigate
extension method that takes a ContentPage
-derived page instance. Therefore, when the NavigateToNoteEntryPage
method executes, the UI defined in the Xamarin.Forms NoteEntryPage
will be displayed, as shown in the following screenshot:
When the NoteEntryPage
is displayed, tapping the back arrow will pop the FrameworkElement
for the NoteEntryPage
from the in-app back stack, returning the user to the FrameworkElement
for the NotesPage
class.
Enable page resizing support
When the UWP application window is resized, the Xamarin.Forms content should also be resized. This is accomplished by registering an event handler for the Loaded
event, in the MainPage
constructor:
public MainPage()
{
// ...
this.Loaded += OnMainPageLoaded;
// ...
}
The Loaded
event fires when the page is laid out, rendered, and ready for interaction, and executes the OnMainPageLoaded
method in response:
void OnMainPageLoaded(object sender, RoutedEventArgs e)
{
this.Frame.SizeChanged += (o, args) =>
{
if (noteEntryPage != null)
noteEntryPage.Layout(new Xamarin.Forms.Rectangle(0, 0, args.NewSize.Width, args.NewSize.Height));
else
notesPage.Layout(new Xamarin.Forms.Rectangle(0, 0, args.NewSize.Width, args.NewSize.Height));
};
}
The OnMainPageLoaded
method registers an anonymous event handler for the Frame.SizeChanged
event, which is raised when either the ActualHeight
or the ActualWidth
properties change on the Frame
. In response, the Xamarin.Forms content for the active page is resized by calling the Layout
method.
Enable back navigation support
On UWP, applications must enable back navigation for all hardware and software back buttons, across different device form factors. This can be accomplished by registering an event handler for the BackRequested
event, which can be performed in the MainPage
constructor:
public MainPage()
{
// ...
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}
When the application is launched, the GetForCurrentView
method retrieves the SystemNavigationManager
object associated with the current view, then registers an event handler for the BackRequested
event. The application only receives this event if it's the foreground application, and in response, calls the OnBackRequested
event handler:
void OnBackRequested(object sender, BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
e.Handled = true;
rootFrame.GoBack();
noteEntryPage = null;
}
}
The OnBackRequested
event handler calls the GoBack
method on the root frame of the application and sets the BackRequestedEventArgs.Handled
property to true
to mark the event as handled. Failure to mark the event as handled could result in the event being ignored.
The application chooses whether to show a back button on the title bar. This is achieved by setting the AppViewBackButtonVisibility
property to one of the AppViewBackButtonVisibility
enumeration values, in the App
class:
void OnNavigated(object sender, NavigationEventArgs e)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
}
The OnNavigated
event handler, which is executed in response to the Navigated
event firing, updates the visibility of the title bar back button when page navigation occurs. This ensures that the title bar back button is visible if the in-app back stack is not empty, or removed from the title bar if the in-app back stack is empty.
For more information about back navigation support on UWP, see Navigation history and backwards navigation for UWP apps.