Migrating From Xamarin - Forms To .NET MAUI - 2025
Migrating From Xamarin - Forms To .NET MAUI - 2025
Xamarin.Forms
to .NET MAUI
A Comprehensive Guide
—
Iris Classon
Migrating from
Xamarin.Forms to
.NET MAUI
A Comprehensive Guide
Iris Classon
Migrating from Xamarin.Forms to .NET MAUI: A Comprehensive Guide
Iris Classon
Mölndal, Sweden
v
TABLE OF CONTENTS
Alternatives .........................................................................................................11
.NET Alternatives ...........................................................................................12
Non-.NET Alternatives ...................................................................................12
The Future of .NET MAUI .....................................................................................14
Release Schedule ..........................................................................................14
Summary.............................................................................................................15
vi
TABLE OF CONTENTS
vii
TABLE OF CONTENTS
viii
TABLE OF CONTENTS
Styling ...............................................................................................................158
Styling Limitations .......................................................................................160
Shadows ......................................................................................................161
Summary...........................................................................................................161
Index .................................................................................................223
ix
About the Author
Iris Classon isn’t new to the XAML tech scene.
Having navigated the waters of Silverlight,
Windows Phone, Windows Store, Xamarin, and
now MAUI, her expertise is broad and deep.
Currently, she’s in the trenches, part of a team
steering a large-scale, complex application
over a million users, from Xamarin.Forms to
MAUI. This not only fuels her insights in this
book but also ensures that the guidance comes
from someone who’s well-versed in making the
leap from theory to action in the world of app migration.
Iris has held the title of Microsoft MVP for 12 years and has authored
half a dozen technical books on .NET. Additionally, she has produced
a wide array of Pluralsight and YouTube tutorials, along with Microsoft
Learn tutorials and numerous hands-on workshops catering to a range
from beginners to advanced users. She frequently speaks at conferences
and user groups and keeps an active blog. Her entire professional life is
dedicated to teaching, learning, and disseminating information about
.NET, her platform of choice. Writing is a particular passion of hers; having
published 13 books, some of which have been translated into several
languages, it’s clear that writing is not only her preferred way to share
knowledge but also something she deeply enjoys.
xi
About the Technical Reviewer
Denis Kondratev has devoted more than
19 years to software development in various
fields. For more than ten years, he was deeply
involved in the field of security systems, where
he applied and developed his skills in C++ and
.NET. Currently, he is engaged in developing
computer games using artificial intelligence
technologies. He had the opportunity to work
on games such as Homescapes, Left To Survive,
and others, which have earned hundreds of
millions of dollars. His articles have been published on popular technical
platforms, such as hackernoon.com and dzone.com. He also regularly
speaks at professional conferences, sharing his experience and knowledge
with colleagues. You can learn more about him on LinkedIn (https://
www.linkedin.com/in/kondratev-denis).
xiii
Acknowledgments
This book would not have come to life without the support and expertise of
many talented individuals.
First, my sincere thanks to my editors, Deepa Tryphosa and Shonmirin
P. A., for their invaluable insights and careful attention to detail. Your
guidance has been instrumental in shaping this work.
To Denis Kondratev, our technical reviewer, thank you for your
thorough and expert approach in ensuring the accuracy of the content.
A special thanks to Ryan Byrnes, acquisitions editor, for championing
this project from the start.
I am incredibly grateful to my colleagues Johannes, Pontus, and Roger,
whose hard work and dedication carried much of the heavy lifting in our
migration efforts. To the rest of the team at Plejd—Max, Andreas, Marcus,
Stefan, Daniel, Jimmy, Joel, Herman, David, Victor, and Magnus—thank
you for your commitment, teamwork, and willingness to tackle every
challenge. Working alongside such talented and supportive individuals is a
privilege.
This book is a reflection of all that I’ve learned with you, and I am
grateful to be part of such an exceptional team.
xv
Introduction
Over a decade ago, when I first started learning programming, I created
my very first app, which was a Windows Phone app. The curriculum at
school primarily revolved around web development using Microsoft
technologies; however, my participation in a Windows Phone user group
sparked my passion for Windows Phone. This was my first introduction
to XAML and marked the beginning of a journey that I am still actively
pursuing. I made a hilarious blunder when I forgot to change the app
name in the configuration file when I ported the app to iOS, and as a result,
the installed app ended up being named App1. It took me a week to realize
this after the app was launched, and embarrassment briefly replaced my
sense of accomplishment. Swearing never to repeat that mistake or make
new mistakes, I later realized that I had forgotten to include a divide by
zero check in the algorithm. It’s safe to say that I’ve learned many lessons
throughout the years, and I’ve made it my personal mission to help others
as much as possible by sharing what I’ve learned.
xvii
INTRODUCTION
My first app was a Windows Phone app that was later ported to iOS.
Since App1, I’ve made many applications for Windows Phone and
later Windows Store. I won a hackathon with Shake a Kitty, was featured
on BBC World News with my marathon app, made a training course, and
wrote a book about Windows Store app development. Prior to Xamarin’s
acquisition by Microsoft, I had the opportunity to visit the Xamarin head
office and even got to engage in interesting discussions with Miguel De
Icaza, the former cofounder of Xamarin. On a lighter note, I may have gone
a bit overboard by accumulating more Tamarin Monkeys than any sensible
adult should possess, as they were the beloved mascots of Xamarin. I
still have one or two somewhere in the attic, and I’m sure my sons will be
thrilled if I find the monkeys.
It’s safe to say that I was, and still am, a big fan of cross-platform
development with XAML and C#.
xviii
INTRODUCTION
Importance of Migration
The end of support (EOS) for Xamarin.Forms marks a significant milestone
for those of us who have relied on this framework for cross-platform
xix
INTRODUCTION
xx
INTRODUCTION
xxi
INTRODUCTION
Summary
Over a decade ago, my programming journey began with a Windows
Phone app, sparking a lasting passion for mobile development and
XAML. It’s with this passion that I’ve written this book that guides you
through a migration process using a hands-on, structured approach with
a fictional fitness app, AppForFitness. Each chapter provides background
information, foundational concepts, and practical examples, addressing
common questions, best practices, and alternatives. The end of support for
Xamarin.Forms underscores the urgency of this transition, ensuring apps
remain functional and future-proof with .NET MAUI.
xxii
CHAPTER 1
Common Questions
As developers move from Xamarin.Forms to .NET MAUI, they often have
questions about the changes, requirements, and advantages of the new
framework. In this section, I’ll be addressing the questions that I’ve come
across the most.
2
CHAPTER 1 THE EVOLUTION FROM XAMARIN.FORMS
Do I Have to Migrate?
Yes, migrating from Xamarin.Forms to .NET MAUI is necessary. Support
for Xamarin.Forms ended in May 2024, meaning no further updates, bug
fixes, or security patches will be provided. Apple will likely require Xcode
16 for new apps and updates by mid-2025, and Google Play is expected to
require Android API level 35 or 36 by June 2025. Without migrating to .NET
MAUI, you won’t be able to publish new apps or update existing ones, as
Xamarin.Forms won’t meet these new platform requirements.
3
CHAPTER 1 THE EVOLUTION FROM XAMARIN.FORMS
Summary
For many cross-platform developers, .NET MAUI ticks all the boxes, but
the most important box is future-proofing your application. What started
as the Mono project evolved into Xamarin, then .NET MAUI, and today
we have a framework that has been around for more than a decade. It has
endured a great deal, and as is typical with open source projects, there
are diverse viewpoints. With that said, this is not the only cross-platform
framework out there, and in the next chapter, we will dive into .NET
MAUI’s features, compare it with alternatives, and address its future.
4
CHAPTER 2
Features
Earlier in the book, I briefly touched upon some of the features and
enhancements, but I haven’t delved into them extensively. Let’s examine
those more closely.
Performance
Performance was one of the initial differences that we were absolutely
thrilled about. It blew our mind away, particularly in build and simulator
deployment time. I had gotten used to waiting a couple of minutes for
the solution to build, deploy to the simulator or device, and painfully
watch that splash screen slowly come into view. As soon as we got the app
building with .NET MAUI, the app would be built and deployed in seconds.
Performance has been an important goal for the .NET MAUI team,
and we’ve seen improvements right off the bat with the first release. One
specific test, called LOLs per second, was used to measure page rendering
performance. This test involves rendering a large number of labels with
the text “LOL,” each with random colors and rotations. In this test, .NET 7
MAUI showed a 51% improvement in page rendering speed compared to
.NET 6 MAUI.
6
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
Figure 2-1. LOLs per second measures the number of “LOL” labels
per second. Performance has more than doubled from Xamarin.
Forms to .NET 7 MAUI
Improved Abstractions
Besides performance, my second favorite improvement was more and
better abstractions. With the launch of what was formerly referred to as
.NET Core (.NET 5), Microsoft went all in with abstractions, best practices,
7
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
Hot Reload
Hot Reload in .NET MAUI enables real-time updates to the app’s UI and
logic without restarting the application. This feature significantly speeds
up the development process by allowing developers to see changes
instantly. It really makes UI work less painful and supports live debugging.
.NET MAUI’s Hot Reload feature primarily supports XAML Hot Reload
and does not support C# code reloading directly. This means that while
you can make changes to your XAML code and see them reflected in real
time without rebuilding the application, changes to C# code, such as event
handlers, require a rebuild. Additionally, XAML Hot Reload supports
simultaneous debugging of multiple platforms (using Visual Studio).
However, separate head projects per platform is a requirement, rather than
a single project app. Simultaneous debugging allows you to deploy your
code to both Android and iOS devices simultaneously, so you can see the
changes on both platforms at the same time.
8
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
Border
In the past, I’ve used the Frame control and padding or a third-party
library, but .NET MAUI includes a Border control that allows for easy
customization.
GraphicsView
GraphicsView is a powerful control introduced in .NET MAUI that allows
developers to draw custom graphics and create complex visuals within
their applications. This control is particularly useful for scenarios where
standard UI elements are insufficient, and custom drawing is required.
Some key features are
• Custom drawing
• Animations
• Event handling
• Seamless integration with other controls
9
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
View Shadow
MAUI views have a shadow property, which means we don’t need to
handle the shadow for the different platforms. However, I want to add that
there have been a few issues with the shadow property, in particular with
nested views. And at the time of writing, the shadow property renders
differently on the different platforms. We’ve had to add handlers to set
different shadow properties depending on the platform.
10
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
Alternatives
Since I am not well-versed in the alternatives and there may be changes by
the time you read this book, I will provide a brief overview of a few current
options. I will discuss the advantages and disadvantages, but ultimately
the direct comparison will be determined by you and your team since it
depends on the specific requirements of the project. As an example, our
team was enthusiastic about using Flutter, but the 15 .NET developers
with extensive Xamarin.Forms experience would have to learn new tools,
a new language, create a new pipeline, and a lot more. All of this would be
prohibitively costly. It would be risky because we have product releases
planned years in advance, and those deadlines are not flexible. Therefore,
we cannot afford to be delayed for an extended period. While we were
tempted by the idea of using something completely different and having
fun with it, it could have potentially been an expensive experiment. .NET
MAUI was for us the safest, fastest, and cheapest option.
11
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
.NET Alternatives
Being able to use the existing expertise of .NET developers on your team is
a big plus, but .NET MAUI isn’t the only .NET alternative. Let me provide
you with a few options.
Uno Platform
Uno Platform allows you to build native mobile, desktop, and web apps
with a single codebase using C# and XAML. It focuses on providing a
consistent UI experience across all platforms.
Pros: Strong alignment with Microsoft’s ecosystem, support for WinUI,
and extensive platform coverage
Cons: Smaller community compared to .NET MAUI and sometimes
lagging behind in support for the latest platform features
Avalonia
Avalonia is an open source, cross-platform UI framework for .NET,
primarily targeting desktop applications but also supporting mobile.
Pros: Rich styling capabilities, support for MVVM, and a vibrant open
source community
Cons: Still maturing in terms of mobile support and may lack some of
the out-of-the-box integrations available in .NET MAUI
Non-.NET Alternatives
However, if your team is diverse or have the resources to embark on a
completely new journey (or even take in new developers), then it might be
worth looking into some non-.NET alternatives.
12
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
Flutter
This is the famous, and popular, Google’s UI toolkit for building natively
compiled applications for mobile, web, and desktop from a single
codebase.
Pros: Fast performance, expressive and flexible UI, and a large
community
Cons: Uses Dart instead of C#, which might require learning a new
language for .NET developers
Ionic
This is a framework for building cross-platform mobile and web apps using
web technologies like HTML, CSS, and JavaScript.
Pros: Familiar web development experience, extensive plugin
ecosystem.
Cons: Performance may not be as high as native or other compiled
frameworks.
Titanium
I had to give Titanium a mention, even though I’m not sure how up to
date the framework is. Remember the App1 I mentioned at the start of the
book? Well, when I ported my Windows Phone app to iOS, I used Titanium
and Titanium Studio. Titanium is an older framework for building native
mobile apps using JavaScript.
Pros: Long-standing presence in the market
Cons: Less active development and smaller community compared to
newer frameworks
13
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
Release Schedule
.NET MAUI is released in sync with major .NET versions, typically
following an annual release cadence. The major releases usually occur in
November each year.
For example, .NET 6, .NET 7, and .NET 8 included updates and
improvements to .NET MAUI. In addition, .NET MAUI receives monthly
service updates, similar to the .NET platform, to address bug fixes,
performance improvements, and new features.
Unlike .NET itself, .NET MAUI does not have Long-Term Support (LTS)
versions, meaning each major release is supported for a minimum of six
months after the next major version is released. To maintain support,
developers need to upgrade to the latest major version within this period.
14
CHAPTER 2 .NET MAUI: FEATURES, ALTERNATIVES, AND FUTURE
Summary
.NET MAUI is here to stay, and as the next step in the Xamarin.Forms
evolution, it brings better performance, new and improved controls,
simpler project structure, Hot Reload, and so much more to the table.
However, there are .NET alternatives, such Avalonia, and non-.NET
alternatives, such as Flutter. Only you and your team know what is the best
fit, but .NET MAUI has long-term support, a public road map, and has
been adopted by large enterprise companies. In other words, .NET MAUI is
a robust solution for cross-platform development.
15
CHAPTER 3
Introducing the
Case Study: A Fitness
Application
In this chapter, I’ll introduce the fitness application I built specifically for
this book—AppForFitness. It’s going to be our case study as we navigate the
migration process from Xamarin.Forms to .NET MAUI. This app, designed
to track One Rep Max (1RM) progress across different exercises, will help
us explore the practical challenges and benefits of migrating a real-world
application to MAUI.
Let’s dive into what the app does, how it’s structured, and what we
need to keep in mind as we move to .NET MAUI.
Application Overview
At its core, AppForFitness helps users track their 1RM progress for various
exercises, like the bench press, squat, and deadlift. The app fetches
exercise data from an external API, caches the data, and displays user
progress through interactive charts. The app securely stores local data and
preferences such as app language, user information, and user preferences
for the calculations. It’s a simple, yet incomplete, fitness tool, with custom
effects, deep linking, gesture-based interactions, and real-time localization.
Main Page
The main page of the app provides users with quick access to the core
functionalities. It uses a TabbedPage that lets the user navigate to key
areas like progress tracking, entering new data, and adjusting settings.
The navigation structure is built with a focus on ease of use, leveraging
Xamarin’s tabbed navigation.
Progress Page
The Progress page (Figure 3-1) is the app’s core feature, allowing users to
visualize their One Rep Max (1RM) progress across different exercises and
muscle groups. Using OxyPlot to generate dynamic charts, users can
• View how their 1RM has progressed over time for
various exercises.
• Switch between different 1RM formulas by long-
pressing the chart, updating the data instantly.
18
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Figure 3-1. Progress page shows the 1RM chart and Quick
Add entries
19
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Entry Page
The Entry page (Figure 3-2) allows users to input new workout data. The
user can access this page from the Quick Add or by clicking the tab for
the page or by deep linking from an external source such as a website.
Deep linking allows users to navigate directly to specific pages in the app
from external sources, such as a website or an email. For example, a link
like http://appforfitness.com/entrypage?exerciseName=Squat takes
users directly to the Entry page for the squat exercise without needing to
manually navigate. The app listens for these URLs, extracts parameters
(like the exercise name), and navigates the user to the correct page.
Key features:
• Manually add new workout entries (exercise,
weight, reps)
• Quickly select favorite exercises by clicking the
workout icon
• Accessible directly from deep links, enabling users to
add entries from external sources, like a website
20
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
21
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Settings Page
The Settings page (Figure 3-3) gives users control over key app settings, for
example, the default formula for 1RM. I’ve omitted other settings.
Key features:
• Set a default formula for calculating 1RM.
• Preferred formula is saved as a local user preference.
Figure 3-3. Settings page lets the user select the default formula
22
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
23
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Figure 3-4. Language pop-up lets the user set the default language
for the app
24
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Email Pop-Up
The Email pop-up (Figure 3-5) is designed for direct interaction with users
who need to provide feedback or request support.
Key features:
• Allows users to send emails directly from the app
• Opens the user’s email client with a prefilled template
for ease of use
• Simple, user-friendly pop-up for sending emails
• Integrates directly with the device’s email application
25
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Figure 3-5. Email pop-up contains an email link that will open the
phone email app with prefilled template
26
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Application Structure
The application is a multi-project Xamarin.Forms solution, following the
traditional structure with a shared library (Figure 3-6) and platform-specific
libraries for Android and iOS (Figure 3-7). In this book, we’ll focus more on
Android as, in my experience, migrating Android apps has presented more
challenges compared to iOS. This is also reflected in the number of bug
reports filed in both the Xamarin.Forms and .NET MAUI repositories.
27
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
28
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Technical Features
The application was purposefully created to showcase specific challenges
that arise during a migration from Xamarin.Forms to .NET MAUI. With this in
mind, several features were incorporated to highlight both the complexities
and the improvements that the migration process offers. Let’s take a closer
look at some of the aspects that we will revisit during the migration.
29
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Navigation
Users can quickly log their most recent workout from the main screen by
clicking a button. This is done by using the Navigation object. Navigation
in .NET MAUI has changed (for the better).
Deep Linking
As mentioned earlier, the app supports deep linking, making it possible
for users to navigate directly to specific pages from external sources. This
is particularly useful for linking to the Entry page from the app’s website
or from other external content like blog posts. Deep linking is slightly
different in .NET MAUI.
Localization
The app supports multiple languages, and users can change the language
on the fly without restarting the app. .NET MAUI provides options to
localization implementation.
MessagingCenter
Localization in the app is done through a LanguagePicker that sends
messages via MessagingCenter to update localized strings throughout the
app. MessagingCenter is deprecated in .NET MAUI.
30
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Long-Press Gesture
The app uses long-press gestures to provide quick access to functionality,
such as switching between 1RM formulas on the progress chart. We will
use this to discuss gestures in .NET MAUI.
31
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Custom Renderers
The app has a couple of custom renderers which we’ll use to discuss at the
.NET MAUI equivalent, handlers. For example, we’ll take a look at the app’s
custom image button that supports shadows for transparent images with
irregular shapes. Custom renderers have for many been one of the pain
points when migrating.
Custom Controls
In addition to the shadow effect, the app also features custom controls like
a pop-up layout and a LanguagePicker. These components demonstrate
how custom features can enhance the user experience and will be
streamlined during the migration to MAUI.
Layouts
The app uses a mix of layouts, including StackLayout, RelativeLayout,
and AbsoluteLayout. These layouts structure the UI but come with some
migration considerations. We’ll review these layouts during migration
to ensure compatibility and optimize performance. MAUI also changes
how layout properties like padding and margins are handled, which we’ll
update as needed.
Image Management
The app uses both embedded images and platform-specific resources which
will be helpful for us when we look at image management in .NET MAUI.
32
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
33
Chapter 3 IntroduCIng the Case study: a FItness applICatIon
Later in the book, we’ll dive into some refactoring techniques and look
at how you can improve your app’s architecture as part of the migration.
But for now, let’s keep our focus on the migration itself and how .NET
MAUI can help us clean up and modernize these kinds of projects.
Summary
The fitness application, AppForFitness, was created as a case study to
guide the migration process from Xamarin.Forms to .NET MAUI. The app
is designed to track a user’s One Rep Max (1RM) progress across various
exercises, fetching data from an external API and displaying interactive
charts using OxyPlot. It also includes features like secure storage for user
preferences, deep linking for direct navigation from external sources, and
localization with dynamic language switching.
The app has several pages—Main, Progress, Entry, and Settings—that
each serve a specific purpose for our migration walk-through.
From a technical standpoint, the app highlights key challenges
and opportunities for improvement during migration. These include
managing third-party dependencies, updating navigation patterns, and
handling custom renderers. The app’s use of different layouts, image
management techniques, and effects like custom shadows ensures that we
explore a wide range of migration scenarios, making it an ideal project to
demonstrate the transition from Xamarin.Forms to .NET MAUI.
In the next chapter, we’ll prepare for the migration process by setting
up the development environment and assessing the current state of the
application. We’ll review third-party dependencies, ensure everything is
up to date, and compile a checklist for a smooth migration.
34
CHAPTER 4
Preparing
for Migration
Good preparation is key to a smooth migration, and taking the time to set
things up correctly at the start can save a lot of headaches down the line.
In this chapter, I’ll walk you through the essential steps to get everything
ready before diving into the migration process. We’ll cover setting up your
development environment for .NET MAUI, making sure your Xamarin.
Forms project is using the latest version, and taking stock of your app’s
dependencies and native code.
As this book covers migrating from Xamarin.Forms, I’m assuming you
have a working Xamarin.Forms development environment for your current
app, but I will briefly cover the steps for setting up your environment and a
remote Mac host in a Windows virtual machine (VM). We are going to use
Visual Studio on Windows for parts of the migration. The reason is because
there is a very helpful tool that we can use that has not been ported to Mac
OS at the time of writing. This tool is .NET Upgrade Assistant, a tool that
helps automate much of the migration process by analyzing your Xamarin.
Forms project and making the necessary changes to convert it into a .NET
MAUI project. Although this tool simplifies many steps, it’s currently
only available on Windows, which is why we’ll be using a Windows VM
alongside our Mac setup. Don’t worry; we’ll walk through the manual steps
as well, ensuring you understand every part of the process.
36
Chapter 4 preparing for Migration
37
Chapter 4 preparing for Migration
38
Chapter 4 preparing for Migration
When you run the command above, it installs the .NET MAUI workload
and its associated tooling. By default, it includes support for all the target
platforms that .NET MAUI supports: Android, iOS, macOS, and Windows.
However, to confirm that it is installed for all platforms, you can check
for specific workload packs, such as
• maui-android
• maui-ios
• maui-maccatalyst
• maui-windows
These platforms should be installed automatically as part of the MAUI
workload, but you can check by running
When you run a workload installation command for the first time, it
will take a few minutes.
Setting Up a Windows VM
Setting up a VM is a straightforward process. Use a free trial account if
you don’t have a license for Windows or Parallels (unless you use free
virtualization software).
39
Chapter 4 preparing for Migration
40
Chapter 4 preparing for Migration
Figure 4-2. Pair to Mac can be found under the Tools tab in the
main menu
41
Chapter 4 preparing for Migration
Figure 4-3. By default, your Mac should show up in the list. If you
can't see it there, add it using the IP address from earlier
Premigration Checklist
Before we begin the code inventory, there are a few foundational tasks we
should complete to ensure a clean starting point for the migration. These
steps shouldn’t take long, and while steps 1 and 2 are not mandatory, I
strongly recommend following them.
more time to migrate their apps to .NET MAUI. The extra time allowed
Microsoft to address remaining bugs and compatibility issues and
ensure better support for developers who might not be ready to migrate
immediately.
Another significant reason for this update was Apple’s strict
requirements. Apple regularly releases updates for iOS, macOS, and
Xcode, and they enforce strict compatibility rules. Any app developed
for iOS must be built with a recent version of Xcode to remain compliant
with Apple’s App Store policies (which is also why we have to migrate to
.NET MAUI, if we want the app to run on iOS18). By upgrading to the latest
version of Xamarin.Forms, your project will have improved compatibility
with the latest versions of iOS, Android, and their respective SDKs, which
will in turn make the migration to .NET MAUI easier.
Before you upgrade your NuGet packages, make sure that you have the
appropriate version of Xcode:
1. Install command-line tools:
xcode-select –install
xcodebuild -version
43
Chapter 4 preparing for Migration
xcodebuild -version
You can also set the Xcode version by setting the path for the Xcode
version you want to use in Rider or Visual Studio.
Visual Studio: Tools ➤ Options ➤ Xamarin ➤ iOS Settings
Rider: Settings ➤ Build, Execution, Deployment ➤ Apple Platforms
In addition, get the latest versions of Android SDK for your IDE. If
you are using Rider, then this can be done through the Android SDK
Updater, found in the Preferences window: Preferences ➤ Languages and
Frameworks ➤ Android SDK Updater.
In Visual Studio: Tools ➤ Android ➤ Android SDK Manager.
Build your solution, run any tests you might have, deploy the app, and
verify the app starts without issues.
44
Chapter 4 preparing for Migration
2. Exploratory Testing:
a. Perform thorough exploratory testing across all platforms
(iOS, Android, etc.), using both simulators and physical
devices.
b. Ensure that every feature and functionality works as expected.
c. If any errors or issues arise during testing, fix them
immediately and rerun the tests.
3. Back up the codebase:
a. Version control: I strongly recommend using version control
(such as Git, Bitbucket, GitHub, etc.). If you don’t have it set
up yet, now is the perfect time.
b. Commit changes: Once you’re confident that the app is
updated and running as expected, commit the changes to
your version control system.
c. If you don’t have version control in place, at the very least,
create a manual backup of the entire codebase.
4. Test external entry points:
a. Test deep links and other external entry points to make sure
they behave correctly. The AppForFitness deep link can be
tested by using the terminal:
adb devices
45
Chapter 4 preparing for Migration
46
Chapter 4 preparing for Migration
47
Chapter 4 preparing for Migration
You can use these categories as a starting point, but feel free to
customize them to match the app’s functionality:
• Renderers
• Effects
• Other
48
Chapter 4 preparing for Migration
49
Chapter 4 preparing for Migration
50
Chapter 4 preparing for Migration
Internal Dependencies
When doing the inventory, don’t forget to analyze homegrown libraries
or in-house code that might need updating for .NET MAUI. While
AppForFitness doesn’t have any additional internal libraries, my
experience with the Plejd app has shown that internal dependencies can
often present unique challenges.
In Plejd, we use several internal libraries, some of which are managed
by other teams. For example, the test team manages a library that defines
constants used in our automated UI tests. This kind of internal dependency
can be just as crucial to a successful migration as third-party libraries. If your
app relies on internal libraries, you’ll need to ensure that they are either
updated for .NET MAUI or compatible with the new platform.
52
Chapter 4 preparing for Migration
Platform-Specific Code
As mentioned earlier, when migrating to .NET MAUI, it’s important to
assess your app’s platform-specific code and decide what to keep, refactor,
or eliminate. The good news is that MAUI brings more cross-platform
features, reducing the need for custom code per platform. However, some
existing implementations, like custom renderers and platform-specific
views, will still need attention.
53
Chapter 4 preparing for Migration
Resources
One of the improvements in .NET MAUI is that sharing resources, such
as images, across platforms has become much easier. With the new
framework, you’ll likely need fewer platform-specific resources. However,
it’s still important to include resources in your inventory, even if they don’t
require as much attention as other components.
When documenting resources, focus on where they are located and
how they are accessed in your project (e.g., whether they are embedded
resources, platform-specific images, or shared resources). You don’t need
to spend too much time on specifics at this stage, but it’s helpful to note
their paths and usage patterns to ensure they migrate smoothly.
54
Chapter 4 preparing for Migration
\tree -I 'bin|obj'
function Show-Tree {
param (
[string]$Path = ".",
[int]$Depth = 0
)
55
56
Chapter 4
Table 4-2. Spreadsheet detailing the result of the platform-specific code inventory
Category Component Current MAUI Action Priority Notes
Usage Equivalent
Custom Control iconButton all Use MaUi refactor to high Layout adjustments
platforms handlers handler may be needed
Custom Control Languagepicker all Use MaUi refactor to high Layout adjustments
preparing for Migration
(image) folder
embedded appres.resx all Shared no action Low Standard localization
resource platforms resource needed file
(localization)
(continued)
57
preparing for Migration
58
Table 4-2. (continued) Chapter 4
Category Component Current MAUI Action Priority Notes
Usage Equivalent
Column Breakdown
59
Chapter 4 preparing for Migration
App Navigation
Navigating between pages and views has evolved with .NET MAUI,
and understanding these changes is essential for a smooth migration
from Xamarin.Forms. So make sure to document how navigation is
implemented within your app, highlighting the use of built-in navigation,
custom navigation, and deep linking.
As before, use a spreadsheet. As an example, here is the navigation in
AppForFitness. Use the IDE Find Usages feature (Table 4-3) to find usages
of the type Navigation.
60
Chapter 4 preparing for Migration
Mainpage tabs no
progresspage Built-in (navigationpushasync) Yes
entrypage Built-in (navigationpushasync) no
Languagepopup Custom (popup) no
emailpopup Custom (popup) no
entrypage Deep Link with Built-in (navigationpushasync) Yes
Summary
We’ve now set up our environment, so we can use Visual Studio on
Windows to access a platform-specific migration tool, and we’ve also
updated our project to the latest version of Xamarin.Forms. Additionally,
we conducted a comprehensive inventory of our code, creating
spreadsheets with detailed information on what needs to be addressed
both before and during the migration process. In the next step, we’ll plan
the migration using the insights gathered in this chapter and finalize our
overall migration strategy.
61
CHAPTER 5
Planning Your
Migration
With the data we collected in the previous chapter, we will strategically
plan our migration in this chapter, considering different approaches
to project structure and deciding between a complete or incremental
migration.
By thoroughly examining the main disparities between Xamarin.
Forms and .NET MAUI, we will be ready to transition smoothly to the next
chapter, where we will initiate the migration process with some minor
cleanup addressing some of the easy-to-fix findings from our inventory.
Migration Strategies
When you migrate to .NET MAUI, you can either do a full migration or
an incremental migration where you only migrate specific platforms. For
example, iOS is the most urgent migration due to the reasons I mentioned
at the start of the book. Android, on the other hand, has for many been
more challenging to migrate (mostly due to UI differences), and I know
several companies that kept Xamarin.Forms for Android and focused on
iOS first.
64
CHAPTER 5 PLANNING YOUR MIGRATION
65
CHAPTER 5 PLANNING YOUR MIGRATION
Project Structure
One of the significant changes in .NET MAUI is the introduction of
multitargeting. Unlike Xamarin.Forms, where you had separate projects
for each platform (e.g., iOS, Android), .NET MAUI allows for a single
unified project structure that simplifies cross-platform development by
reducing duplication. This approach was initially the recommended
project structure, as seen in most sample apps, official documentation,
and Microsoft’s educational videos.
However, this change posed challenges for many developers, especially
those with complex apps, who were used to the multi-project structure
of Xamarin.Forms. The unified structure led to pushback from parts of
the community, with many developers preferring the flexibility of having
separate projects for each platform alongside a shared project. In response,
Microsoft eventually added support for a multi-project structure, making it
easier to maintain the old Xamarin.Forms setup.
When .NET MAUI first launched, we tried the multi-project structure
in .NET MAUI as it translated better coming from our current app. But it
was a pain, and we ended up spending more time than I’d like to admit
trying to make the multi-project structure work. Our biggest challenge
was related to missing build targets for Android, especially around AOT
(Ahead-of-Time compilation), even after attempting to disable it.
Fortunately, a lot has changed since then. The multi-project template
added later is much more stable and widely used by many developers
today. It allows for greater flexibility, especially for those who want to stick
to the multi-project structure. For many developers, especially those with
large, complex apps or specific needs, the multi-project setup offers greater
separation between platforms, making it easier to manage platform-
specific resources, custom renderers, or third-party dependencies that
differ by platform.
66
CHAPTER 5 PLANNING YOUR MIGRATION
In this book, I’ll be using the single project structure when we migrate,
to demonstrate the difference and showcase how the unified project
structure simplifies management for most apps. While I’ve personally been
tempted to recommend this approach to all developers, especially because
the single project structure received more attention and “love” early on in
the MAUI lifecycle, it’s not always the best fit for every app.
Project Structure
Xamarin.Forms uses a multi-project structure, where you typically have
separate projects for each platform (Android, iOS, UWP, etc.). In contrast,
.NET MAUI introduces a single project structure, which centralizes
platform-specific code and resources in a single location. This reduces
duplication and simplifies the project setup.
You can still use a multi-project structure in MAUI if preferred, but
migrating to the single project format could simplify resource management
and builds. Use multitargeting in .NET MAUI to include platform-specific
resources in the shared project using folders like Platforms/Android,
Platforms/iOS, etc.
Design Patterns
Built in to the .NET MAUI framework, you’ll see examples of design
patterns that are also extensively used in ASP .NET Core and other newer
.NET frameworks. These patterns offer more flexible, scalable, and
maintainable codebases. Here are some.
67
CHAPTER 5 PLANNING YOUR MIGRATION
Handler Pattern
We’ll cover this in more detail further down, but custom renderers are
replaced with Handlers, using the handler pattern, which allow more
modular and flexible customization of platform-specific control behavior.
Handlers can also be extended or replaced using something called
mappers.
Builder Pattern
The builder pattern is extensively used, particularly in configuring the
MauiApp. It follows the familiar App.CreateBuilder() pattern seen in ASP.
NET Core for setting up services, middleware, and platform-specific
configurations. It centralizes your service registrations, configurations, and
platform specific.
Service Pattern
In .NET MAUI, the service pattern is where services (e.g., LocationService,
NotificationService) are injected into your pages and ViewModels, further
decoupling UI from logic.
These are just some of the many design patterns .NET MAUI has made
first-class citizens, following the pattern (pun intended) we see overall for
newer .NET frameworks.
68
CHAPTER 5 PLANNING YOUR MIGRATION
UI Differences
When upgrading to .NET MAUI, one of the biggest changes is how
layouts and controls behave compared to Xamarin.Forms. Some layout
behaviors have changed, and default values for spacing and padding have
been adjusted, as well as internal logic. Here’s a breakdown of the major
difference.
Frame
In Xamarin.Forms, Frame controls had inconsistent behavior across
platforms, especially when it came to measuring padding. In .NET
MAUI, Frame has been replaced with Border, which offers consistent
measurement across all platforms. However, Frame is still supported for
migration purposes. Consider transitioning to Border, and use styles to
adjust padding and behavior.
69
CHAPTER 5 PLANNING YOUR MIGRATION
Grid
The biggest change in Grid is that .NET MAUI does not infer columns
or rows as Xamarin.Forms did. You now have to explicitly declare
ColumnDefinitions and RowDefinitions.
StackLayout
In .NET MAUI, StackLayout now behaves more predictably. It will stack
child elements until all have been added, even if it exceeds the available
space. In Xamarin.Forms, StackLayout sometimes expanded or stopped
based on available space. .NET MAUI also introduces VerticalStackLayout
and HorizontalStackLayout, and Microsoft recommends that you use those
instead of defaulting to StackLayout. For layouts that need to subdivide
space, use Grid. Properties *AndExpand (e.g., FillAndExpand) are ignored
in VerticalStackLayout and HorizontalStackLayout, hence the Grid
recommendation.
RelativeLayout
.NET MAUI discourages the use of RelativeLayout, which can only be
used if you include the Microsoft.Maui.Controls.Compatibility package.
Whenever possible, opt for using a Grid instead.
ScrollView
In .NET MAUI, the ScrollView automatically expands to fit its content
unless you set specific constraints. This can be tricky for Xamarin.Forms
users because, in a VerticalStackLayout—which can grow indefinitely—
the ScrollView will stretch to the full height of its content and won’t allow
scrolling. We had quite a few issues with ScrollView when we migrated, so
take note if your app uses ScrollView.
70
CHAPTER 5 PLANNING YOUR MIGRATION
Accessibility
.NET MAUI improves accessibility with better support for Semantic
Properties, which help make your app more accessible across platforms.
There’s also a namespace change, which is an easy fix when we migrate.
Localization
Localization in .NET MAUI has been simplified. All resource files (.resx)
are centrally located in the shared project, and MAUI provides better
support for switching languages dynamically by using the CultureInfo API
to dynamically switch between languages.
71
CHAPTER 5 PLANNING YOUR MIGRATION
Performance
.NET MAUI brings significant performance improvements, especially with
AOT (Ahead-of-Time) compilation and optimized startup times. AOT
improves
• Startup performance: Because the code is already
compiled into native code, there’s no need for JIT
compilation, which significantly reduces the time it
takes for your app to launch.
• Memory usage: AOT can reduce memory consumption
since runtime compilation structures aren’t necessary.
72
CHAPTER 5 PLANNING YOUR MIGRATION
73
CHAPTER 5 PLANNING YOUR MIGRATION
Summary
The fifth chapter concludes our journey before we commence our
migration. In this chapter, we’ve outlined our migration strategy, focusing
on the choice between complete and incremental migrations and project
structure. We discussed key differences between Xamarin.Forms and
.NET MAUI, especially in terms of project structure, UI behavior, lifecycle
management, and device APIs. We explored design patterns that have been
made first-class citizens and reviewed the changes in layout behavior and
resource management in MAUI. Additionally, we covered performance
improvements, such as Ahead-of-Time (AOT) compilation, trimmed
assemblies, and linker optimizations that enhance app performance.
In the next chapter, we’ll address some of the findings from our
inventory, introduce the .NET Upgrade Assistant, and kick off the
migration process with a focus on project structure, UI migration, and
platform-specific integrations.
74
CHAPTER 6
Executing the
Migration
We’ve finally arrived at the core part of the book—the migration! With
all the preparation we’ve done, the migration should proceed smoothly,
though some additional cleanup is still needed. In this chapter, we’ll go
through some minor refactoring opportunities before using the .NET
Upgrade Assistant.
Terminology
To ensure clarity and consistency, we’ll refer to the various projects as
follows:
• Shared project: The Xamarin.Forms shared project that
contains cross-platform logic but no platform-specific
code. This project is where most of the non-platform-
specific business logic, UI, and services reside.
• Android platform project: The Xamarin.Forms project
that contains Android-specific resources and code.
It manages platform-specific integrations, services,
and resources related to Android (e.g., MainActivity,
AndroidManifest.xml).
76
CHAPTER 6 EXECUTING THE MIGRATION
Removing Newtonsoft
Newtonsoft is currently used in the FitnessApiService in the
FetchFromApiAsync method:
77
CHAPTER 6 EXECUTING THE MIGRATION
3. Run and test the app, then commit the changes and
push them to a separate branch (e.g., migration
branch).
When migrating from Newtonsoft.Json to System.Text.Json, there are
notable differences in functionality which you should be aware of.
For example, System.Text.Json lacks support for some features
available in Newtonsoft.Json Many of these are not supported by design.
For example:
1. System.Text.Json requires property names to be in
quotes and does not support single quotes around
string values.
2. Non-string JSON values can’t be deserialized into
string properties.
3. Advanced settings like TypeNameHandling.All for
handling polymorphic types are not supported.
78
CHAPTER 6 EXECUTING THE MIGRATION
https://learn.microsoft.com/en-us/dotnet/standard/
serialization/system-text-json/migrate-from-newtonsoft
Removing SkiaSharp
SkiaSharp is used in the CustomImageViewOutlineProvider class, but
we don’t need advanced features or optimizations for our simple image
resizing task. We can replace SkiaSharp with Xamarin.Android’s built-in
image resizing capabilities.
79
CHAPTER 6 EXECUTING THE MIGRATION
Rg.Plugins.Popup.Popup.Init(this);
80
CHAPTER 6 EXECUTING THE MIGRATION
Make separate commits for each of these changes and push them to
the remote repository.
Now that we’ve cleaned up unnecessary code and dependencies, we’re
ready to start the migration by using the .NET Upgrade Assistant. This tool
will handle much of the heavy lifting by updating our project structure and
converting key components to their .NET MAUI equivalents.
Let’s proceed!
81
CHAPTER 6 EXECUTING THE MIGRATION
82
CHAPTER 6 EXECUTING THE MIGRATION
Once installed, the Upgrade Assistant can guide you through much of
the migration process, leaving you with fewer manual changes to handle.
However, you’ll still need to review your project for more complex cases,
such as third-party library compatibility and custom platform code. This
is particularly important for projects that use Xamarin-specific features,
custom renderers, or platform effects.
83
CHAPTER 6 EXECUTING THE MIGRATION
As for the CLI tool, which will be our preferred method in this book,
we’ll simply use our favorite terminal or command-line tool, navigate to
the solution root, and run
upgrade-assistant upgrade
Navigation
Exit
84
CHAPTER 6 EXECUTING THE MIGRATION
In-Place Migration
What it does: This option upgrades your project directly without
creating a copy.
When to choose it:
Side-by-Side Migration
What it does: This creates a copy of your project and upgrades the copy,
leaving the original project intact.
When to choose it:
• If you want to keep the original Xamarin.Forms project
unchanged, allowing you to maintain it while you
experiment or slowly transition parts of the app to
.NET MAUI.
85
CHAPTER 6 EXECUTING THE MIGRATION
If you want to skip the step by step, you can use the following line:
86
CHAPTER 6 EXECUTING THE MIGRATION
You’ll see the details of the migration if you scroll up, although they
might not be so easy to read. Copy the terminal output to a file for a better
overview. In Visual Studio, the Upgrade Assistant Window will show the
results using icons for the result.
For example, for this app, most of our items have a green checkmark,
but a couple have a lighter shade. These have the following message:
In Visual Studio, you can view the output in the Output window by
using the drop-down to select Upgrade Assistant (Figure 6-2).
Figure 6-2. While the CLI tool shows the output in the terminal, the
extension in Visual Studio outputs the details in the Output window
87
CHAPTER 6 EXECUTING THE MIGRATION
If you have several SDKs installed in your solution, and it causes build
problems, I highly recommend adding a global.json file to the migrated
shared project specifying the SDK version and roll forward policy and
whether to allow prerelease versions:
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMajor",
"allowPrerelease": false
}
}
The migrated shared project is going to use the structure we had for
our shared Xamarin.Forms project, with some additions such as new
dependencies. But most noticeable is the MauiProgramExtensions.cs. This
class extends the configuration of the MauiAppBuilder by introducing a
custom extension method, UseSharedMauiApp. This method allows us
to configure our app and platform logic and is called as part of the app’s
startup pipeline. When you create a brand-new Single Project .NET MAUI
project, you’ll get a MainProgram.cs instead, which essentially does the
same thing.
We’ll cover the structure of a .NET MAUI project later in this chapter
when we create our main .NET MAUI project, where we’ll move all the
code. As mentioned earlier, the migrated shared project is only temporary.
Once we have addressed the errors and made it build, we will move the
content to a new project.
But, before we do anything more, we’ll commit the work in progress
with an appropriate commit message, for example:
88
CHAPTER 6 EXECUTING THE MIGRATION
Updating Dependencies
Generally, I’d say that the best place to start after running the Upgrade
Assistant is dealing with dependencies. Our inventory list is of great help,
and we can use it to identify the dependencies that need an upgrade or
replacement.
Based on the details from the Upgrade Assistant, AppForFitness
doesn’t have a lot of issues that need addressing, but there is one that we
can address: the OxyPlot dependency. If we start a build, we’ll also get
an error:
89
CHAPTER 6 EXECUTING THE MIGRATION
90
CHAPTER 6 EXECUTING THE MIGRATION
(byte)(mauiColor.Blue
* 255));
}
}
xmlns:oxyPlot="clr-namespace:OxyPlot.Maui.
Skia;assembly=OxyPlot.Maui.Skia"
Updating Namespaces
You’ve probably noticed by now that the Upgrade Assistant didn’t
change the namespaces to match the new project name. Since this is an
intermediary project, we don’t really need to update the namespaces
as we’ll have to update them again once we move the code. Renaming
the namespaces is an easy fix as both Rider and Visual Studio has quick
actions for this (Figure 6-4). If you use an IDE that doesn’t support this,
then a Find and Replace can do the trick as well.
Figure 6-4. Both Rider and Visual Studio have quick actions to do
refactoring, such as renaming namespaces
Quick actions are nice, but won’t cover all the references that need
renaming. For example, in XAML files if you’ve referenced the project
assembly, you’ll have problems as the assembly has a new name. Find this:
assembly=AppForFitness"
91
CHAPTER 6 EXECUTING THE MIGRATION
assembly=AppForFitnessMaui"
92
CHAPTER 6 EXECUTING THE MIGRATION
Note that properties that are tied to a specific compatibility control, for
example, RelativeLayout.XConstraint, will also need a reference:
<StackLayout Padding="10">
<Frame HasShadow="False">
<Frame.Effects>
<effects:ShadowEffect />
</Frame.Effects>
<StackLayout>
<compatibility:RelativeLayout Padding="10"
VerticalOptions="Start">
<customControls:IconButton x:Name="image"
Clicked="OnIconClicked"
Source="dumbells.png"
HeightRequest="70"
WidthRequest="70"
compatibility:RelativeLayout.
YConstraint="{compatibility:
ConstraintExpression
Type=Constant, Constant=10}"
compatibility:RelativeLayout.
XConstraint="{compatibility:
ConstraintExpression
Type=Constant,
Constant=10}"/>
93
CHAPTER 6 EXECUTING THE MIGRATION
Sort out the error in this category one by one, commit, push, and try to
build. If you encounter build errors that are not related to dependencies,
code logic, references, namespaces, or alike, have a look at Appendix B.
94
CHAPTER 6 EXECUTING THE MIGRATION
95
CHAPTER 6 EXECUTING THE MIGRATION
Dependencies
This section lists the NuGet packages and the SDK versions your project
targets.
Platforms
These folders contain platform-specific code and resources. If you need to
write code or include assets that are platform specific, you do that in these
folders. This is the equivalent of our platform projects in Xamarin.Forms
and where we’ll move our files.
96
CHAPTER 6 EXECUTING THE MIGRATION
For example:
• In Android, you might have the AndroidManifest.xml
and platform-specific resources.
• In iOS, you’ll have iOS-specific files such as the
Info.plist.
Resources
This is a shared resources folder that allows you to manage cross-platform
resources centrally:
• AppIcon: This folder contains the icons for the app, and
these icons are shared across all platforms.
97
CHAPTER 6 EXECUTING THE MIGRATION
App.xaml/App.xaml.cs
This is the entry point for your application. It defines shared resources for
your app and initializes the app’s components. It is where global styles and
resource dictionaries can be defined.
AppShell.xaml/AppShell.xaml.cs
.NET MAUI uses the Shell concept to simplify navigation in your app. The
AppShell.xaml file contains your app’s navigation structure (e.g., tabs,
flyouts, routes), which can be customized to manage how users navigate
between pages.
MainPage.xaml/MainPage.xaml.cs
This is the default starting page of the application. It is created by default
when you create a new .NET MAUI project.
MauiProgram.cs
This file is the main configuration file for your app. It follows a similar
pattern to ASP.NET Core’s Startup.cs and is where services, configurations,
fonts, handlers, and dependencies are registered using the MauiApp.
CreateBuilder() method.
98
CHAPTER 6 EXECUTING THE MIGRATION
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-
maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPl
atform('windows'))">$(TargetFrameworks);net8.0-
windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will
need to install tizen by following this: https://
github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-
tizen</TargetFrameworks> -->
To this:
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;</
TargetFrameworks>
<OutputType>Exe</OutputType>
99
CHAPTER 6 EXECUTING THE MIGRATION
Also, remove the folders for the targets that were removed. If on
Windows, make sure the folder was in fact deleted by double-checking in
File Explorer or using the command line.
Build the new project, verifying everything works.
Commit and push to remote source control.
<PackageReference Include="OxyPlot.Maui.Skia"
Version="1.0.1" />
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls"
Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls.
Compatibility" Version="$(MauiVersion)" />
100
CHAPTER 6 EXECUTING THE MIGRATION
<PackageReference Include="Microsoft.Extensions.
Logging.Debug" Version="8.0.0" />
<PackageReference Include="OxyPlot.Maui.Skia"
Version="1.0.1" />
</ItemGroup>
Step 3: Drag in the files from the migrated shared project to the
.NET MAUI project. Update namespaces; clean up MainProgram.cs if
needed. Commit and push.
Test Deploying
If we try to deploy the app now, we’ll encounter an error because we
haven’t added .UseMauiCompatibility() to the MauiProgram.cs file. This
method is essential for maintaining backward compatibility with Xamarin.
Forms components that haven’t yet been updated to work with native
.NET MAUI code.
To fix this issue, you’ll need to modify your MauiProgram.cs file. Here’s
how you add .UseMauiCompatibility() to the builder pattern:
101
CHAPTER 6 EXECUTING THE MIGRATION
fonts.AddFont("OpenSans-Semibold.ttf",
"OpenSansSemibold");
});
return builder.Build();
}
}
using Microsoft.Maui.Controls.Compatibility.Hosting;
builder.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
102
CHAPTER 6 EXECUTING THE MIGRATION
builder
.UseMauiApp<App>()
.UseMauiCompatibility()
// Add these two:
.UseSkiaSharp()
.UseOxyPlotSkia()
.ConfigureFonts(fonts => {
fonts.AddFont("OpenSans-Regular.ttf",
"OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf",
"OpenSansSemibold");
});
As you can see, even without the platform-specific code we can get
somewhat up and running.
103
CHAPTER 6 EXECUTING THE MIGRATION
[assembly: Dependency(typeof(SecureStorageService_iOS))]
104
CHAPTER 6 EXECUTING THE MIGRATION
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf",
"OpenSansRegular");
});
return builder.Build();
}
}
105
CHAPTER 6 EXECUTING THE MIGRATION
106
CHAPTER 6 EXECUTING THE MIGRATION
107
CHAPTER 6 EXECUTING THE MIGRATION
<TabbedPage.Children>
<!-- TODO add back -->
<!-- <views:ProgressPage Title="Progress"/> -->
<views:EntryPage Title="New Entry"/>
<views:SettingsPage Title="Settings"/>
</TabbedPage.Children>
IServiceProvider
To make MainPage work without rewriting too much of our logic, we
could, for example, inject the IServiceProvider. The IServiceProvider is a
core interface in .NET’s dependency injection (DI) system and can be used
to retrieve services from the DI container. If we register the ProgressPage in
the builder, we can do something like this:
108
CHAPTER 6 EXECUTING THE MIGRATION
Handler.MauiContext.Services
Another option is to use the OnHandlerChanged method which exists in
the base class and can be overridden. This is a part of the page lifecycle,
which we’ll cover soon.
Inside it, we can check if the Handler for the class is available, with its
MauiContext, and if so access the DI container and retrieve an instance of
ISecureStorageService.
This approach is used to safely access services in MAUI when you
don’t have direct access to the dependency injection container through
constructor injection. It’s a way to bridge the gap between MAUI’s UI
lifecycle and the application’s service container; however, as mentioned
earlier, while this method works, it’s generally preferable to use constructor
injection when possible, as it makes dependencies more explicit and easier
to manage and test, but then the rest of the class has to follow suit.
With the constructor injection removed, I set the storage service like
this instead:
109
CHAPTER 6 EXECUTING THE MIGRATION
While this works, and our app builds, we will get back to this when we
discuss navigation in the next chapter. But for now, let’s commit and push,
happy that we have a working app (albeit without platform-specific code).
Summary
In this chapter, we took our first big steps in migrating the AppForFitness
solution from Xamarin.Forms to .NET MAUI. We started with important
cleanup tasks, including removing unused dependencies like Newtonsoft
and SkiaSharp, simplifying the codebase in preparation for migration.
We used the .NET Upgrade Assistant to automate some aspects of the
migration, converting project structures and updating key components.
The focus was on upgrading the shared project, creating a new .NET
MAUI project and moving the files. As we have dealt with errors and
configurations, we’ve learned more about some of the default design
patterns in .NET MAUI, and we will cover more of that as we migrate.
In the next chapter, we will dive deeper into platform-specific
migration and UI differences and more.
110
CHAPTER 7
Migrating Platform
Projects
With the shared project successfully migrated, it’s time to focus on the
platform-specific projects (iOS and Android). While the app may now
build, it’s still far from fully functional. For many projects, particularly
more complex ones, the app may not even build at this stage. You may
encounter issues because some platform-specific code hasn’t yet been
migrated. For instance, we created a temporary dummy implementation
for our storage service earlier, but if your app contains many such services,
it could become a tedious and complex task.
AppDelegate
In AppDelegate, we had some deep linking logic to navigate to specific
pages when the app is opened through a URL. We’ll need to migrate
this logic to the new AppDelegate.cs in .NET MAUI. Here’s how you can
update it:
using AppForFitnessCore.Services;
using Foundation;
using UIKit;
112
CHAPTER 7 MIGRATING PLATFORM PROJECTS
namespace AppForFitnessCore
{
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() =>
MauiProgram.CreateMauiApp();
This code ensures that the app can still handle deep linking on iOS as it
did in Xamarin.Forms. We’ll deal with navigation and deep linking further
in the navigation section of this book.
113
CHAPTER 7 MIGRATING PLATFORM PROJECTS
Find: public\s+class\s+(\w+)Renderer
Replace: public class $1Handler
114
CHAPTER 7 MIGRATING PLATFORM PROJECTS
115
CHAPTER 7 MIGRATING PLATFORM PROJECTS
116
CHAPTER 7 MIGRATING PLATFORM PROJECTS
if (Control == null)
return;
if (e.NewElement is IconButton)
{
SetDropShadow();
}
}
117
CHAPTER 7 MIGRATING PLATFORM PROJECTS
Refactored version:
public class IconButtonHandler : ImageHandler
{
protected override void ConnectHandler(UIImageView
platformView)
{
base.ConnectHandler(platformView);
118
CHAPTER 7 MIGRATING PLATFORM PROJECTS
119
CHAPTER 7 MIGRATING PLATFORM PROJECTS
Handler Lifecycle
In .NET MAUI, the handler lifecycle revolves around various events and
methods that manage the creation, connection, and disconnection of
platform-specific native controls with the cross-platform UI elements.
Here’s an overview of two main lifecycle events all handlers have:
• ConnectHandler
• This method is invoked when the handler is
connected to its platform view (i.e., when the
cross-platform control is rendered into a platform-
specific view).
120
CHAPTER 7 MIGRATING PLATFORM PROJECTS
TabbedPageRenderer
The TabbedPageRenderer.cs was used to force iOS to use custom styling
for the tabs. Since we aren’t sure if we need this anymore, we’ll just
comment out the handler instead of migrating it and leave a //TODO
comment so we don’t forget about it.
LongPressViewRenderer
We migrated this renderer to a handler that extends the
ContentViewHandler. Initially, I used the ViewHandler by mistake.
However, since the custom control LongPressView inherits from
ContentView, it made more sense to extend the ContentViewHandler to
ensure we are matching the handler to the control’s hierarchy.
121
CHAPTER 7 MIGRATING PLATFORM PROJECTS
Example: IconButtonMapper
To demonstrate, let’s use the IconButton, a control we’ve referred to earlier,
where shadows were added via a custom handler. Using mappers, we can
achieve the same functionality with less code.
122
CHAPTER 7 MIGRATING PLATFORM PROJECTS
123
CHAPTER 7 MIGRATING PLATFORM PROJECTS
handlerCollection
.AddHandler(typeof(LongPressView),
typeof(LongPressViewHandler));
IconButtonMapper.MapIconButton(); // Register the
IconButton mapper here
});
AppendToMapping
This method is used to add additional behavior to an existing mapping
without overriding it completely. You can append platform-specific
behavior or cross-platform extensions:
ImageHandler.Mapper.AppendToMapping(nameof(IconButton),
(handler, view) =>
{
// Add platform-specific logic for IconButton
});
ReplaceMapping
This method is used to replace an existing mapping entirely. This allows
you to redefine how a property or behavior is handled for a specific
platform or control:
124
CHAPTER 7 MIGRATING PLATFORM PROJECTS
ImageHandler.Mapper.ReplaceMapping(nameof(IconButton),
(handler, view) =>
{
// Replace default behavior with custom logic
});
ModifyMapping
Similar to ReplaceMapping, this method allows you to modify an existing
mapping, but instead of completely overriding it, you can change parts of
the behavior:
ImageHandler.Mapper.ModifyMapping(nameof(IconButton), (handler,
view, action) =>
{
// Modify existing behavior
action?.Invoke(handler, view); // Call the original mapping
if needed
});
RemoveMapping
If you want to remove a specific mapping, this method allows you to do so.
It’s helpful if you want to stop applying certain platform-specific behavior.
ImageHandler.Mapper.RemoveMapping(nameof(IconButton));
Preprocessor Directives
If you register mappers outside of the platform-specific folders (like
Platforms/iOS), you’ll need to account for platform differences. In such
cases, use preprocessor directives to conditionally compile the code for
each platform. For example:
125
CHAPTER 7 MIGRATING PLATFORM PROJECTS
#if ANDROID
// Android-specific shadow properties (if needed)
#endif
});
}
}
126
CHAPTER 7 MIGRATING PLATFORM PROJECTS
Effects
Handlers in .NET MAUI are more powerful and customizable compared
to the effects model in Xamarin.Forms. This means that, in many cases,
effects might not be necessary as handlers or mappers can provide the
required customization. However, effects are still supported in .NET MAUI
and can be useful for lightweight, reusable functionality. Migrating your
effects from Xamarin.Forms to .NET MAUI is straightforward and requires
minimal changes, so you can continue using them if needed.
Our ShadowEffect was declared in the shared project and had an
implementation for each platform with an attribute used by the framework
to find the effect. In .NET MAUI, effects are moved to an Effects folder in
the main part of the project, and the platform-specific implementation
is added in the same file with conditional compilation as we discussed
earlier.
127
CHAPTER 7 MIGRATING PLATFORM PROJECTS
/// <summary>
/// Represents a shadow effect that can be applied to UI
elements.
/// </summary>
public class ShadowEffect : RoutingEffect
{
/// <summary>
/// Initializes a new instance of the <see
cref="ShadowEffect"/> class.
/// </summary>
public ShadowEffect()
: base($"AppForFitness.{nameof(ShadowEffect)}")
{
System.Diagnostics.Debug.WriteLine("ShadowEffect
constructor called in shared project");
}
}
}
assembly: ResolutionGroupName("AppForFitness")]
[assembly: ExportEffect(typeof(AppForFitness.iOS.Effects.
ShadowEffect), nameof(ShadowEffect))]
namespace AppForFitness.iOS.Effects
{
public class ShadowEffect : PlatformEffect
{
protected override void OnAttached()
{
128
CHAPTER 7 MIGRATING PLATFORM PROJECTS
try
{
var view = Container;
if (view != null)
{
view.Layer.ShadowColor = UIColor.Black.
CGColor;
view.Layer.ShadowOffset = new CoreGraphics.
CGSize(-2, 2);
view.Layer.ShadowOpacity = 0.4f;
view.Layer.ShadowRadius = 4;
}
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Cannot
set property on attached control. Error: {ex.
Message}");
}
}
129
CHAPTER 7 MIGRATING PLATFORM PROJECTS
using Microsoft.Maui.Controls.Platform;
#if IOS
using UIKit;
#elif ANDROID
// Add android specific references here
#endif
namespace AppForFitnessCore.Effects
{
/// <summary>
/// Represents a shadow effect that can be applied to UI
elements.
/// </summary>
public class ShadowEffect : RoutingEffect
{
public ShadowEffect()
{
System.Diagnostics.Debug.WriteLine("ShadowEffect
constructor called in shared project");
}
}
#if ANDROID
internal class PlatformShadowEffect : PlatformEffect
{
protected override void OnAttached()
130
CHAPTER 7 MIGRATING PLATFORM PROJECTS
{
// TO be implemented once we move over Android code
}
view.Layer.ShadowColor = UIColor.Black.
CGColor;
view.Layer.ShadowOffset = new CoreGraphics.
CGSize(-2, 2);
view.Layer.ShadowOpacity = 0.4f;
view.Layer.ShadowRadius = 4;
}
}
131
CHAPTER 7 MIGRATING PLATFORM PROJECTS
...
.ConfigureMauiHandlers(handlerDelegate)
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf",
"OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf",
"OpenSansSemibold");
});
builder.ConfigureEffects(effects =>
{
effects.Add<ShadowEffect, PlatformShadowEffect>();
});
...
132
CHAPTER 7 MIGRATING PLATFORM PROJECTS
133
CHAPTER 7 MIGRATING PLATFORM PROJECTS
Figure 7-3. Some of the folders and files we are copying over
Next, move the Assets folder. We’ll deal with the specifics of assets and
resources in the following chapter, but for now, place them in the Android
platform folder.
• Update namespaces: As we did for iOS, rename the
namespaces to reflect the new structure.
134
CHAPTER 7 MIGRATING PLATFORM PROJECTS
Summary
In this chapter, we tackled the migration of platform-specific projects,
focusing first on iOS, followed by Android. We discussed how, after
migrating the shared project, many apps may still be incomplete or even
fail to build due to missing platform-specific code. We explored how to
address this by migrating platform logic and handling dependencies, such
as temporary implementations for services, and other quick fixes that we
have to address later.
We also delved into key changes in .NET MAUI, such as how the
Handler pattern has replaced the renderer pattern from Xamarin.Forms.
The chapter highlighted the migration of specific components to handlers
while introducing the concept of mappers. At the end of the chapter, the
AppForFitness app is building, but a lot of work remains. However, with
the foundation from this chapter and the last and our inventory, we are
ready to dive into assets, resources, UI design, navigation, and splash
screens in the next chapter.
135
CHAPTER 8
Migrating the UI
and Navigation
The AppForFitness app is somewhat functional at this stage, but visually
there is a lot of work to be done. The styling still looks off, and platform-
specific discrepancies are affecting the appearance and behavior of certain
controls. For example, our layout controls are rendering differently, and
some styling was lost during migration. These styling issues are minor
tweaks, in particular as .NET MAUI has unified a lot of the control styling,
which means we need less customization to ensure a consistent look and
feel across all platforms.
In addition, we still don’t have a splash screen, haven’t fully addressed
resources, and haven’t fixed the styling for various UI elements. Since
tweaking the visual appearance is mostly a matter of fine-tuning, we’ll
save styling for last and instead focus on one of the most significant
improvements in .NET MAUI: navigation.
.NET MAUI has introduced a more efficient and streamlined
navigation system compared to Xamarin.Forms, and we want to leverage
these enhancements to our advantage in the AppForFitness app.
await Shell.Current.GoToAsync(nameof(ProgressPage));
138
Chapter 8 Migrating the Ui and navigation
We certainly don’t want to mix the two, and using GoToAsync with
Shell is our best approach. Shell has PushAsync, which uses stack-based
navigation. However, GoToAsync is more flexible. When you navigate using
GoToAsync(), it still maintains a navigation stack under the hood works on
top of the navigation stack, but with added flexibility through route-based
navigation.
Let’s clean up our navigation!
AppForFitness Navigation
To improve the navigation structure, we’ll make the following changes:
• Replace the TabbedPage in MainPage with AppShell.
This will allow us to manage navigation and tabs more
declaratively and make future extensions easier.
139
Chapter 8 Migrating the Ui and navigation
140
Chapter 8 Migrating the Ui and navigation
ContentTemplate="{DataTemplate
views:SettingsPage}"/>
</TabBar>
</Shell>
The code-behind file looks the same as MainPage.cs, but I suspect that
our pop-up doesn’t need the current complicated logic that it has. Due to
TabbedPage limitations with adding a control on top of it, we had to create
a grid, insert the content, and overlay the pop-up. We’ll get back to this as
soon as we’re done with the navigation. The AppShell is then added to the
App.cs constructor.
Before:
Now:
Routing.RegisterRoute("somepage", typeof(SomePage));
or like this:
Routing.RegisterRoute(nameof(SomePage), typeof(SomePage));
141
Chapter 8 Migrating the Ui and navigation
Note While the second option makes sure the routes are updated
if the cage name changes, it does create a dependency whenever
we navigate to that page. declaring constants using nameof will also
create transitive dependencies. however, the best way to deal with
this is outside the scope of this book, so for now we’ll use nameof().
[QueryProperty(nameof(ExerciseName), Config.RouteParams.
exerciseName)]
public partial class EntryPage
{
private string _exerciseName;
142
Chapter 8 Migrating the Ui and navigation
This frees up the constructor from query parameters and leaves space
(metaphorically speaking) to inject dependencies instead. I’ve added
UnescapeString since we’ll be passing in the parameter through external
query parameters.
EntryPage uses the FitnessApiService, like we discussed earlier, we can
use dependency injection to inject dependencies.
If we register the page and FitnessApiService in the service collection,
and the query parameter is defined as a QueryProperty, then everything
just happens by magic:
143
Chapter 8 Migrating the Ui and navigation
builder.Services.AddTransient<FitnessApiService>();
builder.Services.AddTransient<EntryPage>();
If we want to clean this up even more, we can define our magic strings
in the config file:
/// <summary>
/// Contains route parameter constants used for deep linking
and navigation within the application.
/// </summary>
public static class RouteParams
{
/// <summary>
/// The exercise name route param.
/// </summary>
public const string exerciseName = nameof(exerciseName);
}
await Shell.Current.GoToAsync($"///{nameof(EntryPage)}?{Config.
RouteParams.exerciseName}={escapedExerciseName}");
Deep Linking
When we sorted out our navigation, we even sorted out our deep link
navigation without much work. Unfortunately, I ran into some problems
with my deep linking, and although in theory you should be able to just
handle them in App.xaml.cs in the OnAppLinkRequestReceived method,
I was redirected to the app but not the specific page, and the method was
never called. I solved this by catching the routing in the platform-specific
entry points (you can see the solution in Appendix B), but I’m hoping this
issue is resolved by the time the book comes out.
144
Chapter 8 Migrating the Ui and navigation
builder
.UseMauiApp<App>()
.UseMauiCompatibility()
.UseSkiaSharp()
.UseOxyPlotSkia()
.UseMauiCommunityToolkit()
The Popup class handles all the logic, similar to our previous
PopupLayout implementation which I removed after adding the
Community Toolkit.
This brings us to an issue with Shell.ToolBarItems. At the time of
writing, the toolbar items are not displayed on iOS. There are a couple of
workarounds, but I opted to use the items in the Shell.TitleView. You can
read more about the bug and workarounds later in the book.
TitleView code:
<Shell.TitleView>
<HorizontalStackLayout HorizontalOptions="End">
<Button Text=" "
Clicked="OnEmailClicked"/>
<Button Text=" "
Clicked="OnLanguageSettingsClicked"/>
</HorizontalStackLayout>
</Shell.TitleView>
146
Chapter 8 Migrating the Ui and navigation
reflection to auto-register or use partial classes and so on. The options are
many; .NET MAUI and .NET in general are very flexible. Do what works for
your solution.
Layouts
If you’ve run the AppForFitness at this point in the migration, or migrated
your own application, you’ve probably noticed that while the app might
work it certainly looks different. We touched on this earlier in the book,
how layouts and controls are a little bit different in .NET MAUI; here are
some (but not all) differences. From here on, we’ll refer to “controls” as
views as layout controls are also controls. Views, commonly referred to as
controls or widgets, are UI objects such as buttons, borders, images, and
so on. They often have a specific user purpose. In this book, we will be
focusing on layouts, although some views will get a special mention, for
example, Border. Here is a recap from earlier, regarding some layout and
view changes:
• Default layout values: In .NET MAUI, padding, margins,
and spacing default to zero, unlike in Xamarin.Forms.
To maintain previous behavior, we can use implicit styles.
147
Chapter 8 Migrating the Ui and navigation
Infinite Expansion
In .NET MAUI, layouts like VerticalStackLayout and
HorizontalStackLayout can expand infinitely to accommodate their child
elements, unlike Xamarin.Forms where the layout would sometimes stop
expanding based on the available screen space or parent control.
ScrollView
For some views, such as ScrollView, this can be problematic. Since it can
grow indefinitely, the ScrollView may expand to fit all content, resulting
in different behavior from your Xamarin.Forms app. You’ll need to add
constraints or adjust child elements if this is a problem. There are also a
few issues that at the time of writing haven’t been solved yet, for example,
shadow property not being applied to content inside the ScrollView. See
the Appendix B for examples.
148
Chapter 8 Migrating the Ui and navigation
*AndExpand
Properties like FillAndExpand, commonly used in StackLayout in Xamarin.
Forms, are either ignored or behave differently in VerticalStackLayout
and HorizontalStackLayout. Use Grid for more precise control over space
distribution when dealing with expanding content.
The layout engine has been redone, so we’d have more predictable
layout behavior in .NET MAUI in terms of how child elements are arranged
and sized. And while this is helpful when dealing with complex layouts, it
also means we need to rethink our layouts and adjust post-migration.
UI Advice
Many of the problems my team encountered with layouts and controls
were related to mixing .NET MAUI and .NET MAUI Compatibility pack.
The Compatibility package is meant to help the transition to .NET MAUI,
and therefore my recommendation is that you try to use it as little as
possible, if at all.
Given the differences between Xamarin.Forms and .NET MAUI, here
are a few migration tips to get you started:
• If possible, remove reliance on the Compatibility
package. Use the suggestions below to get there.
• Replace StackLayout: Consider using
VerticalStackLayout and HorizontalStackLayout instead
of StackLayout. When more complex layouts are
needed, use Grid for better control and performance.
149
Chapter 8 Migrating the Ui and navigation
150
Chapter 8 Migrating the Ui and navigation
Figure 8-1. Find Usages is a handy feature in many IDEs and lets us
find usages of a type or member
151
Chapter 8 Migrating the Ui and navigation
Images
Our background image, bg.png, was in the shared project and was
migrated to the Resources/Images folder in the .NET MAUI project, but the
dumbbell image is still duplicated, once for each platform. I’ve now moved
it to the Resources/Images.
152
Chapter 8 Migrating the Ui and navigation
Note Keep in mind that Svg files are converted to pngs, so use
the .png extension when referencing them in your app. additionally,
image names should be lowercase containing only alphanumeric
characters or underscores and start and end with a letter character.
this is an android requirement.
MauiImage BaseSize
.NET MAUI lets you set a base size for an image in the project file by setting
the BaseSize and Resize attribute. The BaseSize attribute helps us define a
logical size that the image will use as a basis for resizing. This can be useful
when working with, for example, a background image or thumbnail image
that needs to scale properly across different screen sizes and aspect ratios.
The BaseSize defines the "logical" dimensions that the image is going
to be treated as before it is resized based on the actual screen size. The
values in BaseSize should correspond to the aspect ratio of your original
image to avoid distortion.
In our case, if we want to set a new base size to the background image,
which is 1242 × 2688, and we want to maintain its aspect ratio across
various devices, the BaseSize should be something close to this, but
scaled down to a logical size that works well for the screen resolution and
performance.
153
Chapter 8 Migrating the Ui and navigation
For example:
Icons
Continuing on the topic of image management, managing icon assets
is also a whole lot easier now. For example, you no longer need to
manually handle Assets.xcassets the same way you would in a Xamarin.
Forms project. Instead, MAUI centralizes image handling and simplifies
it through the Resources/Images directory as mentioned earlier. MAUI
will automatically resize images for different platforms without needing
you to manually create multiple image versions for different resolutions.
For example, it will create the appropriate image sizes for Android (hdpi,
xhdpi, xxhdpi, etc.) and iOS (@1x, @2x, @3x).
154
Chapter 8 Migrating the Ui and navigation
<ItemGroup>
<MauiIcon Include="Resources\AppIcon/
appicon.svg" />
</ItemGroup>
Condition="$([MSBuild]::GetTargetPlatformIdentifier
('$(TargetFramework)')) == 'windows'"
<MauiIcon Include="Resources/AppIcon/appiconbg.png"
ForegroundFile="Resources/AppIcon/appiconfg.svg" />
<MauiIcon Include="Resources/AppIcon/appicon.png"
BaseSize="128,128" />
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
155
Chapter 8 Migrating the Ui and navigation
<application
android:allowBackup="true"
android:icon="@mipmap/appicon"
android:roundIcon="@mipmap/appicon_round"
Clean the solution, and uninstall the app, before building and
deploying.
Splash Screen
As with images and icons, splash screens are straightforward to implement
unless you want to do something more complex, for example, animations.
Steps:
1. Add a splash screen image to Resources/Splash and
set build action MauiSplashScreen.
2. Add to the csproj file.
3. For iOS, add to info.plist:
<key>UILaunchStoryboardName</key>
<string>MauiSplash</string>
[Activity(Theme = "@style/Maui.SplashTheme",
MainLauncher = true, ConfigurationChanges
= ConfigChanges.ScreenSize | ConfigChanges.
Orientation | ConfigChanges.UiMode |
ConfigChanges.ScreenLayout | ConfigChanges.
SmallestScreenSize)]
156
Chapter 8 Migrating the Ui and navigation
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<EnableCodeSigning>true</EnableCodeSigning>
<CodesignProvisioningProfile>iOS Development
Profile</CodesignProvisioningProfile>
<CodesignRequireProvisioningProfile>true</
CodesignRequireProvisioningProfile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='
Release'">
<EnableCodeSigning>true</EnableCodeSigning>
<CodesignProvisioningProfile>iOS Distribution
Profile</CodesignProvisioningProfile>
<CodesignRequireProvisioningProfile>true</
CodesignRequireProvisioningProfile>
<CodesignTeamIdentifier>YOUR_TEAM_ID</
CodesignTeamIdentifier>
</PropertyGroup>
After moving images and icons and creating the splash screen, build
and run the app. If everything works and looks as expected, go ahead and
remove the folder and files we moved over from the old platform projects
that are no longer needed. For example, I’ve deleted my drawable folders
for Android and the assets folder for iOS.
157
Chapter 8 Migrating the Ui and navigation
Styling
Styling is what adds that extra oomph to an app, and thankfully .NET
MAUI has continued to build on the flexible and powerful styling options
in Xamarin.Forms. Even more so now that we can centralize styling yet
cater for platform differences with OnPlatform in XAML or conditional
compilation.
<Style TargetType="Label">
<Setter Property="BackgroundColor">
<Setter.Value>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="Crimson" />
<On Platform="Android" Value="Yellow" />
</OnPlatform>
</Setter.Value>
</Setter>
</Style>
.titleViewButton{
background-color: transparent;
color: #3b9651;
}
158
Chapter 8 Migrating the Ui and navigation
<ContentPage.Resources>
<StyleSheet Source="/Resources/Styles/styles.css" />
</ContentPage.Resources>
Specifying class:
<Shell.TitleView>
<HorizontalStackLayout HorizontalOptions="End"
HeightRequest="50">
<Button class="titleViewButton" Text=" "
Clicked="OnEmailClicked"/>
<Button class="titleViewButton" Text=" "
Clicked="OnLanguageSettingsClicked"/>
</HorizontalStackLayout>
</Shell.TitleView>
.NET MAUI has also improved styling options for controls and views,
for example, you can now easily set TabbedPage colors using properties
directly in XAML or C# without needing a platform-specific handler or
renderer like we had in the original implementation.
For example:
<TabbedPage BarBackgroundColor="LightGray"
BarTextColor="Black"
SelectedTabColor="Blue"
UnselectedTabColor="Gray">
<ContentPage Title="Home" />
<ContentPage Title="Settings" />
</TabbedPage>
159
Chapter 8 Migrating the Ui and navigation
Styling Limitations
It’s frustrating, but at some point, we will run into problems with styling
MAUI Views and Controls. For example, at the time of writing, while I can
style some aspects of the Shell and ShellContent, some properties do not
exist and can’t be styled. Nor is there a handler we can use.
I’ve set the same style for the background and foreground colors on
the tab items, but changing font size was not available out of the box. The
reorganization of the Shell and NavigationPage components into Handlers
is not yet complete. Our option at this point would be to reintroduce the
compatibility pack and use the ShellRenderer or create our own Shell
navigation.
Whenever you come across an issue, bug, or limitation, please submit
an issue to the .NET MAUI repository. More on that in Appendix B.
Note if your styling isn’t showing, make sure to check the Styles.
xaml file if you have one. the default for .net MaUi declares a lot
of styles. do a search for the property in your project; you might be
surprised to find several declarations sprinkled throughout. generally,
it’s better to stick to styling declarations in resource dictionaries than
incline styling. if you use an ai assistant with your ide, for example,
rider ai assistant, github Copilot, or augment plugin, you can ask the
ai assistant to locate incline styling and help refactor to centralize
styling.
160
Chapter 8 Migrating the Ui and navigation
Shadows
I’ve spent a lot of time on shadow in Xamarin.Forms, and I’m very happy
about the improvements in .NET MAUI. As mentioned earlier, there is now
a shadow property on views and controls.
In .NET MAUI, shadows are applied to the outline or boundary of the
child elements, not the parent layout, for example, a VerticalStackLayout,
which we used before wrapping it in a Border control. Therefore, even
though you are applying a shadow to the VerticalStackLayout, the shadow
will be drawn around the layout’s content and not the layout frame itself.
The Shadow is applied to the content inside the layout, but not
the entire layout’s boundary as the layout itself doesn’t have a "visual
presence" (frame or border) to cast a shadow. Only the child elements
inside the layout do.
This actually works in our favor. AppForFitness has a custom
IconButton that sets a shadow on a transparent image. The Android
handler uses a custom outline provider to create the shadow which was
causing some lag when the page was rendered. We can now remove
the custom view, handler, and outline provider. In addition, by using
the shadow style we defined for our other shadows, we can get a more
cohesive look.
Summary
In this chapter, we finalized the AppForFitness migration, focusing on
visual design and navigation improvements. We revamped the navigation
by transitioning to Shell, leveraging URI-based routes for simpler and more
flexible navigation management.
We also dealt with styling as some was lost during migration, .NET
MAUI’s unified styling has greatly reduced the need for platform-specific
customization, and we’ve used that to our advantage while also discussing
existing issues and controls that lack styling options.
161
Chapter 8 Migrating the Ui and navigation
162
CHAPTER 9
Figure 9-1. You can search for the obsolete error code to locate
warnings for obsolete types or members
164
Chapter 9 Cleaning Up, testing, and OptimizatiOn
MessageCenter
The first obsolete type we are going to address is MessageCenter. This is
the warning description, which even includes recommended replacement:
‘Microsoft.Maui.Controls.MessagingCenter’ is obsolete: ‘We
recommend migrating to `CommunityToolkit.Mvvm.Messaging.
WeakReferenceMessenger`’.
MessageCenter had several limitations, such as lack of strong typing,
limited scalability, and most of all an easy way to introduce memory
leaks. Replacing it is straightforward as the replacement follows the same
messenger pattern. Here are the steps:
1. Add the CommunityToolkit.Mvvm NuGet package.
165
Chapter 9 Cleaning Up, testing, and OptimizatiOn
plotLabel.Text = string.Format(Appres.ProgressText,
_defaultMainMuscle.Name, _currentFormula.
ToString());
}
166
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Thread Management
Our next two warnings are related to threading and the Device object.
Microsoft.Maui.Controls.Device is obsolete. Instead, we can use
DeviceInfo.
For example, our SecureStorageService had platform-specific
implementations as keychain sharing does not work with a free Apple
ID. Therefore, for debug we wanted to use Preferences to store data.
By using DeviceInfo, we can consolidate the SecureStorageService
implementations like this:
167
Chapter 9 Cleaning Up, testing, and OptimizatiOn
with this:
MainThread.InvokeOnMainThreadAsync is an asynchronous
alternative that automatically checks if the code is already on the main
thread and only dispatches if necessary, making it useful in non-UI-bound
contexts. You can use the MainThread.IsMainThread property if you want
to explicitly check if you are on the main thread.
For example:
168
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Here is a list of other types and members that are obsolete in .NET
MAUI 8 and 9:
• TargetIdiom
• FocusRequest
• MauiImageView
• RootPanel
• MauiTimePicker.DateSelected
• Entry.ControlsEntryMapper
• Layout.Mapper
• Picker.Mapper
• WebView.Mapper
• And many more *.Mapper. Use the handler instead.
• IFontNamedSizeService
• AcceleratorTypeConverter
• PopupManager
• ControlsSwipeMapper
• ControlsToolbarMapper
• SearchBar.MapIsSpellCheckEnabled
• ControlsFlyoutPageMapper
• ControlsScrollViewMapper
• UiContainerView
• FormsTextView.ctr with params
• GradientShader
169
Chapter 9 Cleaning Up, testing, and OptimizatiOn
• LinearGradientShader
• ClickGestureRecognizer.cs
• TemplateBinding
• MessagingCenter.cs
• *AndExpand
• AutomationProperties.*
170
Chapter 9 Cleaning Up, testing, and OptimizatiOn
• AutomationProperties.Name
• AutomationProperties.HelpText
• AutomationProperties.LabeledBy
Depending on the IDE you use, you might not receive warnings for
these obsolete properties, so it’s important to be proactive in replacing
them with their modern equivalents.
171
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Additional Considerations
When implementing accessibility features, here are some important things
to keep in mind:
• Don’t set the Description on Labels: The screen reader
will automatically read the text of a label.
• Avoid setting Description on Entry or Editor elements
on Android: This can interfere with TalkBack actions.
Instead, use the Placeholder or Hint property to
provide accessible information.
172
Chapter 9 Cleaning Up, testing, and OptimizatiOn
173
Chapter 9 Cleaning Up, testing, and OptimizatiOn
174
Chapter 9 Cleaning Up, testing, and OptimizatiOn
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/
Themes/DefaultStyle.xaml" />
175
Chapter 9 Cleaning Up, testing, and OptimizatiOn
if (string.IsNullOrEmpty(selectedTheme)
|| Application.Current is not App { Resources.
MergedDictionaries: { } mergedDictionaries
}) return;
mergedDictionaries.Clear();
switch (selectedTheme)
{
case "Protanopia":
mergedDictionaries.Add(new
ProtanopiaStyle());
break;
case "Tritanopia":
mergedDictionaries.Add(new
TritanopiaStyle());
break;
case "None":
mergedDictionaries.Add(new DefaultStyle());
break;
176
Chapter 9 Cleaning Up, testing, and OptimizatiOn
default:
mergedDictionaries.Add(new DefaultStyle());
break;
}
177
Chapter 9 Cleaning Up, testing, and OptimizatiOn
178
Chapter 9 Cleaning Up, testing, and OptimizatiOn
<Label Grid.Row="5"
Text="{AppThemeBinding Light='Current system theme is
Light', Dark='Current system theme is Dark'}"
TextColor="{AppThemeBinding Light=Black, Dark=White}"
FontSize="Large"/>
Testing
AppForFitness currently doesn’t have any tests, but we’re about to change
that. I’ll walk you through the process of creating tests, with a particular
focus on unit testing. When integrated into your development workflow,
unit tests have a significant impact on improving code quality. They also
serve as both design documentation and functional specifications, clearly
defining how your app should behave.
Writing unit tests for platform-specific code is not common or practical
for all solutions, because unit tests are meant to isolate and test logic,
not platform-specific behaviors like UI rendering or OS-specific APIs
(e.g., camera, GPS, or file system interactions). Platform-specific code is
often tied directly to hardware or OS features, which makes unit testing
less meaningful since those features typically need to be tested in their
respective environments which can be done with UI tests, explorative
testing, and other types of tests.
Unit tests are designed to test business logic in isolation without
dependencies on the environment. For platform-specific code (such as
checking DeviceInfo.Platform in .NET MAUI), the focus should be on
abstracting this code into interfaces or services, which you can mock
in tests.
179
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Unit Tests
Instead of directly testing platform-specific code, a more common
approach is to mock the platform-dependent behavior. This allows you to
test how your app reacts to platform-specific scenarios (e.g., testing what
happens when the app is running on iOS or Android), rather than testing
the platform itself. Let’s implement some unit tests:
1. Add new test project, for example, an xUnit test
project. If you try to add a reference to the .NET
MAUI project, you might get a message that it’s not
compatible (Figure 9-3).
Figure 9-3. You might need to add conditions to your tags in the
csproj file when adding a reference to a .NET MAUI project
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;
net8.0</TargetFrameworks>
<OutputType Condition="'$(TargetFramework)'
!= 'net8.0'">Exe</OutputType>
Figure 9-4. Extract Interface is a nice feature that can help us quickly
add a layer of abstraction
181
Chapter 9 Cleaning Up, testing, and OptimizatiOn
182
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Debugger wrapper:
public interface IDebugger
{
public bool IsAttached { get; }
}
public class DebuggerWrapper : IDebugger
{
public bool IsAttached => Debugger.IsAttached;
}
Dependency registrations:
builder.Services.AddSingleton(DeviceInfo.Current);
builder.Services.AddSingleton<IDebugger, DebuggerWrapper>();
builder.Services.AddSingleton(SecureStorage.Default);
builder.Services.AddSingleton(Preferences.Default);
builder.Services.AddTransient<ISecureStorageService,
SecureStorageService>();
4. Write tests:
183
Chapter 9 Cleaning Up, testing, and OptimizatiOn
_mockPreferences.Object,
_mockSecureStorage.Object);
[Theory]
[InlineData(nameof(DevicePlatform.iOS), true)]
[InlineData(nameof(DevicePlatform.iOS), false)]
[InlineData(nameof(DevicePlatform.Android), true)]
[InlineData(nameof(DevicePlatform.Android), false)]
public async Task StoreDataAsync_ShouldUseCorrect
Storage(string platformName, bool isDebuggerAttached)
{
// Arrange
var platform = GetDevicePlatform(platformName);
_mockDeviceInfo.Setup(d => d.Platform).
Returns(platform);
_mockDebugger.Setup(d => d.IsAttached).
Returns(isDebuggerAttached);
var service = CreateService();
// Act
await service.StoreDataAsync("testKey", "testValue");
184
Chapter 9 Cleaning Up, testing, and OptimizatiOn
// Assert
if (platform == DevicePlatform.iOS &&
isDebuggerAttached)
{
_mockPreferences.Verify(p => p.Set("testKey",
"testValue", null), Times.Once);
_mockSecureStorage.Verify(s =>
s.SetAsync("testKey", "testValue"), Times.Never);
}
else
{
_mockSecureStorage.Verify(s =>
s.SetAsync("testKey", "testValue"), Times.Once);
_mockPreferences.Verify(p => p.Set(It.
IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>()), Times.Never);
}
}
// Code omitted
Integration Tests
Integration tests are designed to test how different parts of your system
work together. Unlike unit tests, which focus on isolated components,
integration tests verify that multiple components (such as classes, services,
or modules) collaborate correctly.
Integration tests for a .NET MAUI application are no different
from other integration tests. Here is one of the integration tests for the
FitnessApiService:
185
Chapter 9 Cleaning Up, testing, and OptimizatiOn
public ApiServiceIntegrationTests()
{
_client = new HttpClient();
_client.BaseAddress = new Uri(Config.BaseUrl);
}
[Fact]
public async Task Get_Exercises_ReturnsSuccessAndCorrectData()
{
// Arrange
var endpoint = Config.ApiExercises;
// Act
var response = await _client.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
// Assert
Assert.NotNull(responseData);
var exerciseCatalogue = JsonSerializer.Deserialize<Exercise
Catalogue>(responseData);
Assert.NotNull(exerciseCatalogue);
Assert.NotEmpty(exerciseCatalogue.Exercises);
}
186
Chapter 9 Cleaning Up, testing, and OptimizatiOn
UI Tests
UI testing in .NET MAUI is somewhat different from Xamarin.Forms,
primarily due to changes in the underlying framework and tools used
for UI testing. However, the fundamental concept remains the same: UI
testing involves automating interactions with the user interface of your
application to validate that the app behaves as expected. UI testing is
beyond the scope of this book, but I do want to mention two tools (and I’m
sure there are more that will pop up over the next few years):
• Appium: This is still a robust, widely used solution that
fully supports .NET MAUI apps across Android and
iOS, leveraging the WebDriver protocol to interact with
mobile applications.
• Maestro: A fast-growing tool with an evolving feature
set that could be an excellent fit for .NET MAUI testing
on mobile platforms.
187
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Even though we have unit and integration tests and UI tests, they
always find edge cases that we’ve missed. Exploratory testing is a manual
testing technique where testers actively explore the application without
following a predefined set of test cases. The focus is on discovery,
investigation, and learning as testers interact with the application in real
time, finding issues that might not be uncovered through traditional
scripted testing.
As the name implies, scenario-based testing is when tests are created
based on specific cases or scenarios. It is also commonly referred to as
use case testing. It’s similar to regression testing which is the process
of running a suite of tests, including previously defined scenarios,
after changes are made to the application to ensure no new defects are
introduced.
Performance
According to Google/SOASTA research, if a page takes one to three
seconds or more to load, bounce rates increase by 32%. While I don’t
know how this would translate to a mobile app, I can imagine the user’s
impatience and expectations are very similar.
There’s no denial that there is limited tooling for performance and
memory profiling due to the complexity of building cross-platform
applications, but there are some. While this performance profiling is out of
scope for this book, I do want to discuss how we can improve performance.
If you’d like to learn more about profiling .NET MAUI applications, have a
read here: https://github.com/dotnet/maui/wiki/Profiling-.NET-
MAUI-Apps.
Performance in .NET MAUI apps is crucial for providing a smooth
user experience across different platforms. Microsoft recommends several
strategies to optimize MAUI app performance:
188
Chapter 9 Cleaning Up, testing, and OptimizatiOn
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ProgressPage
{
[assembly: XamlCompilation(XamlCompilationOptions.
Compile)]
189
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Memory
Memory leaks are a performance nightmare and known to be difficult to
profile and identify. While some performance tools can give a pretty good
indication of where there’s a problem, it’s hard to say if it’s due to memory
without specifically monitoring memory usage. You can use tools for
memory profiling or the good old-fashioned way of manually monitoring
190
Chapter 9 Cleaning Up, testing, and OptimizatiOn
memory usage by the process (if you are running a simulator) or by using
tools. For example, for iPhone apps on Mac, you could use Xcode memory
debugger, Xcode Instruments, Activity Monitor, third-party profiling tools,
or even iOS Settings storage for the app.
But I’ve also come across a fun little library you can add to the app,
MemoryToolkit.Maui. MemoryToolkit.Maui is a NuGet package designed
specifically for .NET MAUI applications to help developers detect and
diagnose memory leaks, and I’m going to use it to demonstrate why
memory leaks, in particular memory leaks, are difficult to identify even
with the help of tools. Let’s have a look:
1. Add the NuGet package AdamE.
MemoryToolkit.Maui.
2. Add to builder:
if DEBUG
// Configure logging
builder.Logging.AddDebug();
191
Chapter 9 Cleaning Up, testing, and OptimizatiOn
3. Add to page:
<ContentPage xmlns="http://schemas.microsoft.com/
dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/
winfx/2009/xaml"
xmlns:mtk="clr-namespace:MemoryToolkit.
Maui;assembly=MemoryToolkit.Maui"
x:Class="ShellSample.MainPage"
mtk:LeakMonitorBehavior.Cascade="True">
<!-- All child views are now monitored for
leaks. -->
</ContentPage>
192
Chapter 9 Cleaning Up, testing, and OptimizatiOn
193
Chapter 9 Cleaning Up, testing, and OptimizatiOn
194
Chapter 9 Cleaning Up, testing, and OptimizatiOn
195
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Trimming
Trimming is a .NET SDK feature that removes unused assemblies and
code from your app, which helps reduce the final size of the app. When
you build your app, in particular for release, there might be many parts of
the libraries you use, such as the .NET Base Class library, that aren’t used
by the app. Trimming removes what isn’t being used, keeping only the
essential bits. By analyzing the code and determining what is used and
what isn’t used, the trimming tool is able to remove what isn’t referenced
by your code. We covered trimming incompatibilities earlier in the book,
but as a refresher here they are:
• Reflection-based serializers (hence why we removed
Newtonsoft).
• Runtime code generation via JIT (System.reflection.
Emit usage)
Configuring Trimming
In your .csproj file, you can enable or adjust trimming by setting the
PublishTrimmed property to true. Here’s a basic example:
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
196
Chapter 9 Cleaning Up, testing, and OptimizatiOn
• Full (default)
• Partial (only assemblies that have opted in to trimming)
Opting in assemblies when TrimMode is set to partial is done with the
TrimmableAssembly tag:
<ItemGroup>
<TrimmableAssembly Include="MyAssembly" />
</ItemGroup>
197
Chapter 9 Cleaning Up, testing, and OptimizatiOn
Configuring AOT
To enable AOT compilation for your Android app, you can add the
following configuration to your .csproj file:
<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>
<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
<UseLLVM>true</UseLLVM>
</PropertyGroup>
Summary
This chapter wrapped up the migration of the AppForFitness app and
provided a comprehensive guide for transitioning a Xamarin.Forms app
to .NET MAUI. For my team, who had to migrate a mature and complex
application (unlike our example app here), the journey was challenging,
198
Chapter 9 Cleaning Up, testing, and OptimizatiOn
199
APPENDIX A
Resources and
Further Reading
Staying informed and equipped with the right resources is crucial when
navigating the evolving landscape of mobile development, especially when
migrating from Xamarin.Forms to .NET MAUI. Whether you’re tackling
technical challenges, optimizing performance, or learning new features,
having access to reliable documentation, tutorials, and tools is essential.
In this appendix, I’ll list some of my favorite and valuable resources,
including official documentation, community blogs, podcasts, GitHub
repositories, and virtual macOS solutions.
202
Appendix A ResouRces And FuRtheR ReAding
https://blog.jetbrains.com/dotnet/
203
Appendix A ResouRces And FuRtheR ReAding
DevExpress Blog
DevExpress, like Telerik, is also a third-party vendor.
Their blog, which is kept up to date by technical
evangelists, covers step-by-step guides on profiling
and performance optimization in .NET MAUI
applications.
https://community.devexpress.com/blogs
Syncfusion Blog
Syncfusion is another vendor with great tools and an
active blog.
https://www.syncfusion.com/blogs/
Microsoft Learn
Microsoft Learn is Microsoft’s official learning
platform, offering a wide range of content on various
technical topics. The platform features code-along
series, deep-dive tutorials, and quick singular videos
that cover topics from basic introductions to advanced
concepts. You can follow along with interactive
tutorials at your own pace. For those working with
.NET MAUI or other Microsoft technologies, you
might even spot me in some of these videos.
https://learn.microsoft.com/en-us/training/
paths/build-apps-with-dotnet-maui/
204
Appendix A ResouRces And FuRtheR ReAding
DotNetConf Sessions
Conference sessions, especially from .NET
Conf 2023 and 2024, where several speakers
demonstrated migrating to MAUI.
https://dotnetconf.net/
Pluralsight
Pluralsight is similar to Microsoft Learn with a
broad catalog of courses and video format learning
resources. Many of the courses have code samples
you can download.
https://www.pluralsight.com/search?q=maui
205
Appendix A ResouRces And FuRtheR ReAding
AI Tools
There are many AI tools available to developers, and new ones are
emerging on a weekly basis. Tools like Rider AI Assistant and GitHub
Copilot are incredibly useful, not only as pair programmers or sources of
documentation (always ask for links and double-check information!) but
also for nontraditional use cases. I’ve been using Copilot and Augment
to help me understand the source code for .NET MAUI and third-party
libraries that I use.
For instance, Augment can analyze a folder of code files or specific files
and help you quickly extract insights. You can ask questions like
206
Appendix A ResouRces And FuRtheR ReAding
Base Implementation
The base implementation of ButtonHandler is defined in
ButtonHandler.cs:
207
Appendix A ResouRces And FuRtheR ReAding
This base class defines the property mapper and command mapper,
which are used to map properties and commands from the cross-platform
abstraction to platform-specific implementations.
Platform-Specific Implementations
The ButtonHandler is then implemented for each platform (Android,
iOS, Windows, etc.) using partial classes. For example, in ButtonHandler.
Android.cs:
208
Appendix A ResouRces And FuRtheR ReAding
Interface Implementation
The ButtonHandler implements the IButtonHandler interface, which is
defined in IButtonHandler.cs:
The rest of the dialogue is omitted, but this shows how powerful AI
tools can be for learning!
209
Appendix A ResouRces And FuRtheR ReAding
MacinCloud
MacinCloud is one of the most popular services
for developers who need access to macOS
environments. It offers various subscription plans,
including Managed Servers, Dedicated Servers, and
Pay-As-You-Go options.
https://www.macincloud.com/
MacStadium
MacStadium specializes in providing enterprise-
grade Mac infrastructure, including Mac mini and
Mac Pro hosting.
https://www.macstadium.com/
210
Appendix A ResouRces And FuRtheR ReAding
VirtualMacOSX
VirtualMacOSX provides macOS instances for iOS
app development, with pricing tiers based on usage.
https://virtualmacosx.com/
Summary
Staying up to date is vital in the fast-paced world of software development.
Migrating an app from Xamarin.Forms to .NET MAUI is just the start of
the journey. Once your app is up and running in .NET MAUI, ongoing
maintenance is essential to ensure it remains performant, secure, and
compliant with the latest platform changes. This is where continuous
learning becomes crucial. By subscribing to official documentation,
reading blogs, listening to podcasts, and following relevant discussions,
you’ll be better equipped to manage your app long after the initial
migration.
211
APPENDIX B
Common Errors
and Problems
Software development often feels like a delicate balance between creating
something functional and unraveling the inevitable complexities that arise
along the way. Debugging, fixing, and resolving issues is an integral part
of every developer’s journey, and migrating to .NET MAUI from Xamarin.
Forms is no exception.
Appendix B covers some of the common environment errors and
problems encountered during the migration process.
Error: NETSDK1178
Error Message:
AOT.Cross.net8.ios-arm64 Microsoft.NETCore.App.Runtime.AOT.
Cross.net8.iossimulator-arm64 Microsoft.NETCore.App.Runtime.
AOT.Cross.net8.iossimulator-x64
Solution:
Add a global.json to the project and specify the correct .NET version,
ensuring you’re not using a prerelease version.
Solution:
Adjust the MtouchLink and PublishTrimmed properties in your project’s
.csproj file:
<PropertyGroup>
<MtouchLink>SDKOnly</MtouchLink>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
Solution:
Ensure that the correct Java SDK version is installed and that your Android
SDK path is correctly configured. Setting the AndroidSdkDirectory in your
project’s .csproj file often resolves the issue.
214
Appendix B common eRRoRs And pRoBlems
Solution:
Ensure you have the correct version of Xcode installed from the App Store,
and check that the Xcode path is set correctly in your IDE (Visual Studio or
Rider). If the latest Xcode version is not supported, downgrading can help.
Solutions:
• Namespace mismatch: Ensure the x:Class attribute
in the XAML file matches the namespace in the
code-behind.
215
Appendix B common eRRoRs And pRoBlems
Platform-Specific Errors
When dealing with platform-specific builds, errors relating to platform
configuration and deployment might arise.
Solution:
If the version is defined in the .csproj file, remove the entry from the Info.
plist file:
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTarget
PlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0
</SupportedOSPlatformVersion>
216
Appendix B common eRRoRs And pRoBlems
Solution:
Ensure that you have an Android Virtual Device (AVD) configured properly
using the AVD Manager in Android Studio. Check if the correct API level is
installed for the target emulator.
Solution:
This occurs when the necessary simulator (e.g., iOS 17.5) isn’t installed in
Xcode.Open Xcode on your Mac, go to Settings ➤ Platforms, and ensure
the required iOS simulator version is installed. You can also run xcrun
simctl list devices to list available simulators.
IDE-Specific Errors
IDE errors can occur when configurations or paths are incorrect, especially
when using third-party IDEs like Rider.
Solution:
Select the correct namespace provider in Rider or ensure the namespaces
are correctly mapped.
217
Appendix B common eRRoRs And pRoBlems
iOS-Specific Errors
iOS deployment and linking often cause challenges during the migration
process.
Solution:
Ensure that you have selected the iPhoneSimulator as the deployment
target in Visual Studio.
Android-Specific Errors
Android-specific issues often arise around intents, deep linking, and
memory handling.
218
Appendix B common eRRoRs And pRoBlems
Solution:
Override the OnNewIntent method in MainActivity to route deep link
requests:
219
Appendix B common eRRoRs And pRoBlems
UI Issues
Migrating UI components can lead to missing elements or incorrect
layouts.
<Shell.TitleView>
<VerticalStackLayout HorizontalOptions="End">
<Button Text=" " Clicked="OnEmailClicked" />
220
Appendix B common eRRoRs And pRoBlems
Solution:
Check your .csproj to ensure the appropriate target platforms are included:
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0</
TargetFrameworks>
Submitting Issues
Even with thorough preparation, some errors will inevitably require
external help. If you encounter bugs or issues, consider submitting them to
the official .NET MAUI GitHub repository:
https://github.com/dotnet/maui/issues/new?assignees=&labels=t%2
Fbug&projects=&template=bug-report.yml
221
Index
A, B AppShell navigation, 138, 139
Artificial Intelligence (AI)
Accessibility
Augmentlink, 207
automation properties, 171
ButtonHandler, 207
considerations, 172
code/specific files, 206
features, 171
Copilot/Augment, 206
SemanticProperties, 171
interface implementation, 209
SettingsPage, 173
Mac environments, 210
Ahead-of-Time (AOT), 197, 198
platform-specific
Android project, 134, 135
implementations, 208
AOT, see Ahead-of-
social media/discussion
Time (AOT)
platforms, 209
AppForFitness
code structure, 33
migration process, 34
MVVM pattern, 33 C
navigation, 139 CLI, see Command-line
1RM (see One Rep Max (1RM) tool (CLI)
progress) Command-line tool (CLI), 82, 83
side-by-side migration VS (see Visual Studio)
compatibility package, 92
dependencies, 89–91
long-term support (LTS), 86 D
namespaces, 91–94 Deep link navigation, 144
output window, 87 Dummy implementation, 111
OxyPlot, 89, 90 Dynamic theme switching
SDK version, 88 (AppTheme), 174–179
E I
Environment errors/problems, 213 Integration tests, 185–187
Android-specific issues, iOS platform project
218, 219 Android (see Android project)
application bundle, 218 AppDelegate, 112, 113
IDE errors, 217 features/patterns, 133
ILLINK, 214 handler pattern, 116
iOS deployment and cross-platform code, 116
linking, 218 IconButtonRenderer, 117
Java/Android SDK, 214 ImageButtonHandler, 118
MinimumOSVersion lifecycle events, 120
value, 216 platform-specific
namespaces/missing code, 116
components, 215 register platform, 119
NETSDK1178, 213 renderers, 116–118
platform-specific builds, 216 mappers vs. handlers
simulator runtime, 217 append, 124
submitting issues, 221 IconButtonMapper, 123, 124
test project, 221 MauiProgram.cs, 123
UI issues, 220 modification, 122, 125
virtual device issues, 216 remove, 125
Xcode/macOS replace, 124
dependencies, 215 working process, 122
namespace renaming/
cleanup, 113–115
F preprocessor directives
Fitness application, see effects model, 127–132
AppForFitness .NET MAUI, 126
ShadowEffect, 127
source code, 125
G, H Regex search and replace, 115
GitHub repository, 205 renderers, 121
224
INDEX
225
INDEX
226
INDEX
227
INDEX
228
INDEX
ReSharper, 49 UI rendering/OS-specific
sharing resources, 54 APIs, 179
SkiaSharp, 50 Universal Windows Platform
spreadsheet, 50, 51 (UWP), 65
views/controls, 54 UWP, see Universal Windows
foundational tasks, 42 Platform (UWP)
NuGet packages, 43
solid backup, 44–46
Xamarin.Forms, 42 V, W
Xcode version, 44 Visual Studio
CLI tool, 82
extension manager, 82
S extension tool, 84
SkiaSharp, 79, 80 project context menu, 83
terminal code, 84
T
Testing
X, Y, Z
exploratory tests, 187, 188 Xamarin.Forms, 1
integration, 185–187 advantages, 2
scenario-based testing, 188 apps, 3
UI testing, 187 cross-platform developers, 4
unit tests, 180–185 DependencyService, 104
Thread management, 167–170 differences, 4
evolution, 2
image/resource
U management, 152
Unit testing background image, 152, 153
abstractions, 181 BaseSize attribute, 153, 154
.csproj file, 180 icons, 154–156
platform-specific code, 179, 180 splash screens, 156, 157
SecureStorageService. libraries/plugins, 4
cs, 182–185 migration (see Migration
process)
229
INDEX
230