What Happens to Your Xamarin.Forms NuGet Packages in .NET MAUI

The dependency list is where Xamarin.Forms migrations actually stall. Not the renderers, which are tedious but tractable, and not the project file, which is a day. The stall is a package that four screens depend on, whose last release was in 2021, and whose maintainer's repository is archived.

Finding that out in month three is expensive. Finding it out before you start is a morning's work, and this is the method.

Build the inventory first

Start from the actual package references, not from memory:

dotnet list package --include-transitive

Run it against each project in the solution. Then, for every package, record five things: current version, latest published version, date of the latest release, whether it advertises .NET MAUI or a modern net*-android / net*-ios target framework, and how many files in your codebase reference it.

That last column is the one people skip and the one that determines everything else. A dead package used in one screen is a deletion. The same package used in forty is a project.

The four buckets

Every dependency lands in one of four groups.

Moved into the framework. Some functionality that was a separate package in Xamarin.Forms is now part of .NET MAUI. Device and platform APIs that lived in Xamarin.Essentials are the big example: they are in the box now, under a different namespace. The migration for these is a namespace change and the deletion of a package reference. They are free wins, and worth doing first because they shrink the list.

Renamed or re-targeted. The library still exists and still has a maintainer, and there is a .NET MAUI package — often with a different package id, sometimes with a reorganised API. The work is a version bump plus whatever the maintainer's migration notes say. Read the release notes rather than assuming a drop-in: libraries frequently take a major version as the chance to change their own API.

Replaced. No .NET MAUI package from that maintainer, but the functionality is well covered elsewhere, or is now small enough to own yourself. This is the bucket where you make a real choice, and the criteria we use are: is it maintained, does it target current platforms, and how much of its surface do you actually use? A charting package where you use one chart type is often better replaced by a hundred lines you control than by another dependency that will age the same way.

Gone. No successor, no equivalent, and the functionality is not trivial. These are the ones that determine the migration plan.

The categories that reliably cause trouble

Commercial control suites. If your app uses a paid control suite, check two things early: whether your licence still entitles you to the current release, and whether the controls you actually use exist in the .NET MAUI package. Both answers come from your vendor, and both take longer to get than you expect. Start that email in week one.

Native binding libraries. A binding against a vendor's native SDK is two problems: the binding project itself has to be rebuilt for the new target frameworks, and the underlying native SDK may have moved several major versions — or stopped shipping. Scanning, payments, mapping and device-hardware SDKs are the common cases. Establish whether a supported native SDK still exists before you plan anything around it.

Anything that reached into the framework's internals. Packages that subclassed Xamarin.Forms internals, hooked the application lifecycle, or depended on renderer internals will not survive, because the thing they reached into no longer exists in that shape.

Push notifications, analytics and crash reporting. Usually fine, sometimes a full SDK replacement because the vendor's product itself was retired. Worth checking early because they touch app startup.

MVVM and messaging helpers. MessagingCenter is deprecated in .NET MAUI; the community-maintained weak-reference messenger is the usual replacement and the conversion is mechanical. DependencyService still works but is worth replacing with the built-in dependency injection while you are in the file anyway — the app already has a service collection at startup.

Sequencing what you find

The order that works:

  1. Delete the packages whose functionality is now in the framework. Immediate, and it shortens the list.
  2. Bump the ones that just need a version. Also immediate.
  3. Start the licence and vendor conversations for commercial suites and native SDKs. These have external latency and nothing you do makes them faster.
  4. Design the replacements for the "gone" bucket, and — importantly — check whether the screens that depend on them should exist at all. A dead dependency behind a flow nobody uses is a retire decision, not a re-implementation.

Write down the ones you cannot solve

The thing we insist on in an assessment is that unresolved dependencies are written down with a named owner and a decision date, rather than carried as a general feeling that "the chart thing might be a problem". A migration plan with three open dependency questions in it is honest and workable. The same plan with those questions unstated is the one that slips a quarter.

A note on doing this yourself

This is genuinely a task your own developers can do, and they should: they know which screens matter. The inputs are the package list, your crash and analytics data, and a few vendor emails. What it costs is a focused morning and the willingness to record "no successor" when that is the answer.

If the list comes back with native bindings on it and nobody has rebuilt a binding project before, that is the point where it is worth talking to someone who has.