In the earlier post, we’ve seen about the SDK-style project file details, but it is also vital to look at the unified project structure to get into the application flow for each platform.
In this Single project structure, both platform-specific and platform-independent code will live in the same space.
Update: Take a look at this article to know more on the application startup that is pertaining to the latest preview (Preview 8 as of Sep 2021).
Note: The structure and flow that is depicted in this article correspond to that of Preview 3 and as it is still in the development phase, it may tend to vary in the upcoming previews as it takes shape. Will try to do a follow-up article whenever there is a noticeable change in the flow.
As it is multi-targeting by design, there are three ways in which platform-specific code can be added to this project.
- Folder level – A special folder designated for each of the platform. iOS, Android, MacCatalyst (and hopefully Windows, once it becomes part of this ecosystem).
- File level – Platform name will be included as part of the filename and during build, appropriate source file will be automatically picked up based on target platform set. This is useful to maintain related source files together. Esp. when defining Handlers (extension point, think of Renderers in Xamarin.Forms). Implementation of ButtonHandler shown below.
- ButtonHandler.cs (platform-independent)
- ButtonHandler.Standard.cs (.NET Standard implementation)
- ButtonHandler.Android.cs
- ButtonHandler.iOS.cs
- ButtonHandler.Mac.cs
- ButtonHandler.Windows.cs
- Preprocessor Directives – If there is a need to use platform-specified code within a single source file, then this approach will come in handy (like Startup.cs, where lifecycle events of a platform is handled. This is similar to that of shared asset project used in early days of Xamarin.Forms).
Here global.json, NuGet.config, and Directory.Build.targets are configuration files and it’s all for working with the preview bits.
global.json configures the SDK to be used to build this project, NuGet.config has the details of the repository to look for the packages referenced while restoring them and Directory.Build.targets define the Single Project capability (a workaround until it is standardized as a workload).
Before looking into the flow in detail, we need to look at the five main interfaces:
- IStartup
- IApplication
- IWindow
- IPage
- IView
We all know that the HostBuilder pattern (Microsoft.Extensions.* packages) is battle-tested in ASP.NET Core applications for quite so long, but it is designed to work without any Web dependency so that it can be used in any .NET Core apps (and .NET 5 or later as it is unified now). Now it’s time for .NET MAUI to leverage it.
First, IStartup interface introduces the Configure method, which will be invoked from the platform code during App initialization to configure the DI container with services, handlers, compatibility renderers, lifecycle events, fonts, essentials, and so on.
Next, IApplication interface introduces the CreateWindow method, and this gets linked into the flow via the UseMauiApp<App>() generic method call.
Here App inherits from the Application base class, which in turn implements the IApplication interface and marks the CreateWindow method as virtual so that the derived class can implement it by overriding.
Then, IWindow interface, which introduces two properties: Page and MauiContext
As MAUI is designed to work as Desktop Apps as well (Windows and macOS for now, maybe Linux can join later), there is a need to introduce the concept of Window. Since mobile apps work with a single-window. Hence in Xamarin.Forms, there is no such requirement. Dual screen is another representation of the view within a single window.
Since desktop apps might require multiple window definitions. So I’ve introduced an intermediate base class, named Window, implementing the interface, and then defined the MainWindow class by inheriting from it.
Window object that is returned from the CreateWindow method call will be the primary window and the Page object that is contained within that will be the first page to show up in the app (the equivalent of MainPage in Xamarin.Forms).
Finally, IPage interface introduces the View property, which is a way to access the actual content of the page.
For this to work, any page that inherits from ContentPage should implement this interface and expose the content via View property (of type IView).
Using the earlier approach, have introduced another intermediate base class, named MauiPage, inheriting from ContentPage and implementing the IPage interface.
Now the startup page, titled MainPage, inherits from this new MauiPage base class. In the constructor of the MainWindow, an instance of this MainPage is created and assigned to the Page property.
Now we’ve covered the main interfaces in detail. It’s time to see how it is chained in each platform in a seamless way.
The common routine would be as below:
IStartup (Configure method call) → IApplication (CreateWindow method call) → IWindow (Instance of Primary window) → IPage (Instance of the Startup page) → IView (Content)
Any lifecycle event defined in Startup will be hooked at the appropriate place.
iOS and MacCatalyst:
Since both are from Apple, they follow a similar approach.
- Execution starts from the Main() method defined in Program.cs
- In which, UIApplication.Main() method is invoked with a reference to AppDelegate class
- Now, AppDelegate inherits from MauiUIApplicationDelegate<Startup>
- Notice the Startup linkage here
- Generally, AppDelegate’s FinishedLaunching() method is where all the initialization will happen, now it is abstracted into that base class
- Internally, the Configure method of Startup will be invoked, from thereon it’s a common routine as depicted above
Android:
- Execution starts from the class that is marked with the [Application] attribute, MainApplication.cs
- Now, MainApplication inherits from MauiApplication<Startup>
- Notice the Startup linkage here
- Generally, the Application’s OnCreate() method is where all the initialization will happen, now it is abstracted into that base class
- Internally, the Configure method of Startup will be invoked, from thereon it’s a common routine as depicted above
- Post initialization, Activity set with MainLauncher as true will the startup activity, here it’s MainActivity.cs
- Now, MainActivity inherits from MauiAppCompatActivity
- Activity’s OnCreate() method gets called and it is now abstracted into the base class which does all the heavy lifting
We’ll take a closer look at the application flow of the Windows platform once it is included in this single project ecosystem.
And in the next article, will look into how Handlers work in MAUI.
Stay connected as we continue to learn and share the experiences from this exciting journey of being a software developer.
2 replies on “.NET MAUI – Project Structure and Application Flow”
Thanks a lot, keep up the good job with these MAUI posts!
LikeLike
Keep connected, will learn and share on this Maui journey.
LikeLike