GUIDE MIGRATION

Xamarin to Flutter migration

Moving from Xamarin to Flutter is an application rebuild, not a syntax conversion. This guide explains how to evaluate the investment, preserve production behavior, and release safely without disrupting existing users.

Why migrate from Xamarin to Flutter?

A xamarin to flutter migration replaces more than a mobile UI framework: it changes the programming language, rendering model, dependency ecosystem, and delivery workflow. For decision-makers, the challenge is funding a sustainable replacement without losing business momentum. For practitioners, it is preserving behavior, local data, and native integrations while rebuilding the client.

Microsoft ended support for Xamarin, including Xamarin.Forms, on May 1, 2024. Existing applications do not automatically stop working, but unsupported tooling creates growing exposure to operating-system changes, SDK requirements, and dependency incompatibilities. Microsoft’s Xamarin support policy establishes the lifecycle context.

Flutter is a credible replacement when an organization wants a shared Android and iOS interface and is willing to adopt Dart. It is not a direct upgrade path. Xamarin.Forms XAML, C# view models, and platform renderers do not translate automatically into production-ready Flutter code.

The first decision should therefore be whether Flutter fits the product and team, not merely whether Xamarin needs replacing.

Decide whether Flutter is the right destination

Compare Flutter with .NET MAUI and native development against the application’s actual requirements. A prototype of a difficult workflow provides better evidence than a generic framework comparison.

Decision criterionFlutter is attractive whenInvestigate alternatives when
Team capabilityThe team can adopt Dart and own Flutter architecturePreserving C# skills and libraries is the dominant constraint
Interface strategyAndroid and iOS should share a consistent, customized UIPlatform-specific experiences outweigh shared presentation
Native integrationsRequired vendor SDKs have usable plugins or bridgeable APIsCritical SDKs have restrictive support or difficult native lifecycles
Existing code investmentMost client logic can be rewritten independentlyExtensive reusable .NET client libraries drive application value
Platform scopeMobile is primary and other targets can be validated separatelyDesktop or specialized hardware requirements dominate
Delivery capacityA replacement can be funded alongside maintenanceThe team cannot sustain overlapping implementations

Choose Flutter on demonstrated fit, not assumed code sharing. A shared codebase still needs platform-specific permission handling, signing, device testing, and release management.

.NET MAUI deserves evaluation where retaining .NET investment matters. Native Swift and Kotlin may be preferable for applications deeply coupled to platform capabilities. Flutter’s desktop and web support should not be treated as proof that a mobile migration automatically solves those targets.

Establish measurable approval criteria

Before approving the rebuild, define acceptance gates such as:

  • Every revenue-critical workflow passes on supported devices.
  • Existing users retain required accounts, preferences, and offline records.
  • Startup time and scrolling meet product-defined budgets.
  • Accessibility works with VoiceOver and TalkBack.
  • Authentication, push notifications, deep links, and background work pass lifecycle tests.
  • The team can build, sign, release, and diagnose both platform binaries.

Measure the current app first. “Faster than Xamarin” is not a usable acceptance criterion without a workload, device, and measurement method.

Audit the Xamarin application before estimating

A Xamarin.Forms application and a Xamarin.Native application have different migration profiles. Xamarin.Forms usually concentrates shared presentation in XAML and view models. Xamarin.Android and Xamarin.iOS applications may contain much more platform-specific UI and lifecycle logic.

Inventory both shared and native projects. Include NuGet dependencies, custom renderers, effects, dependency services, bindings, entitlements, build scripts, and distribution settings.

Classify what survives the migration

Existing assetLikely treatment
REST or GraphQL backendRetain if its contract remains suitable
OpenAPI specificationsReuse to generate or validate Dart clients
C# business logicRewrite in Dart, move selected logic server-side, or replace
XAML layouts and bindingsRebuild using Flutter widgets and state management
Native Java, Kotlin, Objective-C, or Swift codePotentially retain behind a platform integration
SQLite dataPreserve or migrate after validating schema and access details
UI automationRework for Flutter’s widget and semantics structure
Icons, translations, design tokensReuse where formats, licenses, and accessibility allow

Avoid estimating by screen count alone. A simple account screen with biometric login, token renewal, and account switching can carry more risk than several content screens.

Build a dependency replacement matrix recording each package’s purpose, proposed Flutter equivalent, maintenance status, licensing, native requirements, and fallback. Candidate tools might include dio for HTTP, drift or sqflite for persistence, and flutter_secure_storage for sensitive values. Package popularity is not evidence of compatibility with your application.

Plan architecture and native boundaries

Flutter replaces Xamarin’s presentation and binding patterns with a widget tree driven by application state. Preserve useful domain boundaries rather than reproducing the old framework’s structure mechanically.

A practical design separates:

  • Presentation: widgets, navigation, accessibility, and user interaction.
  • Application logic: workflow coordination and state transitions.
  • Domain logic: business rules and value objects.
  • Infrastructure: network clients, databases, analytics, and native integrations.

Riverpod, Bloc, and Provider are established state-management options. Select one based on team familiarity, testability, and workflow complexity. Introducing several competing patterns during migration makes parity harder to verify.

Port behavior, not syntax

C# Task workflows need deliberate translation into Dart Future and Stream behavior. Cancellation, exception handling, nullability, and concurrency semantics require review rather than line-by-line conversion.

Pay particular attention to:

  • Monetary calculations that currently use C# decimal.
  • Time zones, timestamp serialization, and daylight-saving transitions.
  • Enum values and JSON field naming.
  • Retry behavior for non-idempotent requests.
  • CPU-heavy work that could block Flutter’s main isolate.

Create behavior-level tests from the Xamarin implementation. These become executable specifications even when no source code is reusable.

Bridge native capabilities deliberately

When no suitable plugin exists, Flutter can communicate with platform code through platform channels. Pigeon can generate typed messaging interfaces; Dart FFI is relevant for compatible C APIs, not as a general shortcut for retaining C# libraries.

The official Flutter platform channels documentation explains this integration model.

Prototype difficult capabilities early: Bluetooth peripherals, background location, proprietary payment SDKs, embedded camera pipelines, or device-management integrations. A successful emulator demonstration is insufficient for hardware-dependent functionality.

Choose a migration strategy

Full replacement

Build a Flutter application and release it as an update to the existing store listing, preserving the required application identity and signing configuration.

This provides the cleanest destination architecture but increases the period of parallel development. It works best when the product has a manageable feature set and stable backend contracts.

Incremental embedding

Flutter supports add-to-app integration with native host applications. However, embedding Flutter into an existing Xamarin shell is not a turnkey Xamarin migration path. It can require custom native bindings, engine lifecycle management, build integration, and navigation coordination.

Use this approach only after proving the integration on both platforms. Its benefit is incremental replacement; its cost is temporarily maintaining two UI runtimes and a more complex toolchain.

Workflow-first replacement

Build the new app around complete journeys—such as sign-in, browse, purchase, and order tracking—rather than disconnected screens.

This is often the most practical planning approach for a full replacement. Internal and beta releases validate end-to-end behavior before public distribution. Do not ship a reduced replacement unless omitted features are genuinely unnecessary or their removal is explicitly accepted.

Execute the migration step by step

1. Baseline production behavior

Document supported OS versions, device classes, API contracts, analytics events, and failure modes. Capture representative journeys from clean installs and long-lived installations.

Record launch performance, crash patterns, and offline behavior. Separate existing defects from behavior that must remain compatible.

2. Prove the highest-risk integration

Build a small Flutter application containing the hardest native capability, authentication flow, and one realistic API interaction.

Use physical Android and iOS devices. Test permission denial, process termination, and background transitions. If the vendor SDK cannot be integrated sustainably, reconsider the framework before expanding the rewrite.

3. Establish the delivery foundation

Create reproducible builds using GitHub Actions, Azure DevOps, Codemagic, or another suitable CI service. Pin the Flutter SDK and commit dependency lockfiles for the application.

Set up development, staging, and production configurations. Validate Android signing, iOS provisioning, entitlements, and secret handling immediately—not during release week.

4. Implement one vertical slice

Deliver one complete workflow through UI, state, API access, persistence, error handling, analytics, and tests.

Use this slice to establish architecture conventions and measure actual throughput. Revise estimates using integration effort rather than counting completed widgets.

5. Migrate local state and authentication

An in-place store update may retain the application sandbox, but retaining files does not make their contents usable.

Inspect database paths, schemas, encryption, preferences, and secure-storage configuration. A Xamarin secure-storage library and a Flutter plugin may use different keys, service identifiers, or access settings.

Design migrations to be versioned, recoverable, and safe to retry. Test upgrades using populated installations. If sessions cannot be preserved securely, plan reauthentication and explain it clearly to users.

6. Complete features against a parity matrix

Track capabilities rather than screen names. For checkout, include payment cancellation, duplicate submissions, expired sessions, network loss, and server validation.

Record intentional differences and obtain product approval. Keep backend contracts compatible with both clients while adoption remains mixed.

7. Run upgrade and operational rehearsals

Install the Xamarin production version, populate realistic data, and upgrade to the candidate Flutter build.

Verify push token refresh, deep links, file access, purchases, background jobs, and sign-out cleanup. Rehearse support diagnostics and incident response.

8. Release gradually and monitor

Use TestFlight and Google Play testing tracks before public release. Where available, use phased or staged distribution and feature flags to limit exposure.

Monitor technical failures and business journeys together. A crash-free release can still break login or conversion.

Halting a rollout does not downgrade devices already updated. Recovery usually requires a corrective release or a server-controlled mitigation.

Test beyond visual parity

Flutter needs several complementary test layers:

  • Unit tests: domain rules, serializers, retries, and state transitions.
  • Widget tests: forms, loading states, validation, and interaction.
  • Golden tests: selected visual regressions under controlled conditions.
  • Integration tests: complete journeys with realistic backend behavior.
  • Physical-device tests: permissions, biometrics, notifications, and peripherals.

Flutter’s official testing overview describes the core testing layers.

Golden tests do not replace accessibility or interaction testing. Validate text scaling, focus order, semantics, localization, and right-to-left layouts where applicable.

Profile performance in representative release or profile configurations rather than drawing conclusions from debug builds. Include lower-end supported Android devices and realistic network conditions.

Budget for coexistence and long-term ownership

There is no reliable universal migration price or duration. The strongest cost drivers are native integration complexity, local-data compatibility, test coverage, and ongoing feature changes.

Break the estimate into discovery, architecture, feature implementation, native work, data migration, validation, release, and stabilization. Include:

  • Maintenance of the Xamarin app during the rewrite.
  • Dart and Flutter training.
  • Backend compatibility changes.
  • Device testing and CI infrastructure.
  • Plugin maintenance or custom bridge ownership.
  • Store submission work and post-release support.

Build scenarios around named risks. “Payment SDK bridge still unproven” is more useful than an unexplained contingency percentage.

Avoid an unrestricted redesign during migration. Essential usability fixes may be justified, but changing navigation, backend contracts, and business rules simultaneously makes regressions harder to isolate.

Common mistakes that derail Xamarin to Flutter migration

  • Treating conversion tools as the plan: generated translations cannot validate lifecycle behavior or plugin compatibility.
  • Leaving native integrations until last: attractive screens can conceal a blocking SDK issue.
  • Assuming secure storage is interchangeable: encryption and access configuration may differ.
  • Testing only clean installs: existing users bring databases, cached files, sessions, and historical state.
  • Changing analytics events casually: broken event continuity weakens before-and-after comparisons.
  • Ignoring old clients: users do not all update immediately.
  • Expanding platform scope prematurely: adding web or desktop can turn replacement into a larger product program.
  • Defining rollback as binary reinstallation: store distribution and schema changes make recovery more complicated.

For related modernization planning, browse more Migration topics.

Frequently asked questions

Can Xamarin C# code be reused directly in Flutter?

Not in a standard Flutter application. Flutter application code uses Dart. Backend services, specifications, assets, and test cases often remain reusable. Native platform libraries may also be retained through integrations, but running existing .NET client code requires specialized architecture and should not be assumed.

Is Flutter a better migration target than .NET MAUI?

It depends on the constraints. Flutter suits teams prioritizing its UI model and ecosystem while accepting a language change. .NET MAUI may preserve more C# investment. Compare both using a difficult production workflow, required vendor SDKs, and the team’s maintenance capacity.

Can users keep their data and store installation?

Usually the replacement can be distributed through the existing listing when application identifiers and signing requirements are preserved. Data continuity needs separate validation. Sandbox retention does not guarantee compatible database formats, encryption keys, secure-storage access, or session behavior.

How long does a Xamarin to Flutter migration take?

Duration depends more on workflow complexity and integrations than screen count. Estimate after the audit and a production-like vertical slice. Include coexistence, upgrade testing, store distribution, and stabilization; a feature-complete build is not yet a safely completed migration.

Have a question about this topic?

Ask the community and get answers from practitioners.

Start a discussion