First Look at the Migration from Xamarin Native to Flutter

Introduction

Hi! My name is Egor, I currently work at DD Planet, and I have been developing mobile applications for over two years. In this article, I want to share my experience of switching from Xamarin Native to Flutter. I will try to compare the two frameworks from the point of view of personal experience, talk about my old misconceptions about declarative frameworks, which my experience with Flutter debunked, and at the end of the article, I will talk about my ideal mobile dream framework.

This article, in my opinion, will be of interest to those developers without experience working with declarative frameworks who (like me until recently) still use .xml and .xib files to write the interface.

By the way, the second part of the story about the technical details of the transition to the new framework from our team lead Victor is already in the article “How we switched from Xamarin to Flutter”

A small disclaimer: when I use the term “declarative UI” below, I am referring exclusively to the Compose, Flutter, and SwiftUI approaches to building interfaces.

About Xamarin Native Development

The development of the project, for which we were forced to switch to Flutter, was carried out from about 2017-2018. I joined in 2022 with zero experience in developing for mobile platforms (at that time, I had experience exclusively in developing for Desktop on WPF and AvaloniaUI). The project was a social network for neighbors with chats, communities, a feed, a smart home and other services, and the number of screens exceeded a good hundred.

The framework was taken as a basis MvvmCross – the only framework on the market that allows using a shared UI for each platform. The UI for Android was laid out using xml and C# code, and for iOS, xib files and C# were used accordingly.

The team was divided into two parts, each of which was engaged in the layout for a specific platform and writing common code. However, there were also comrades who successfully implemented the interface for both platforms.

Example code of bindings in MvvmCross for View on iOS
var set = this.CreateBindingSet<PeopleView, PeopleViewModel>();

set.Bind(this).For("NetworkIndicator").To(vm => vm.FetchPeopleTask.IsNotCompleted).WithFallback(false);
set.Bind(_refreshControl).For(r => r.IsRefreshing).To(vm => vm.LoadPeopleTask.IsNotCompleted).WithFallback(false);
set.Bind(_refreshControl).For(r => r.RefreshCommand).To(vm => vm.RefreshPeopleCommand);

set.Bind(_source).For(v => v.ItemsSource).To(vm => vm.People);
set.Bind(_source).For(v => v.SelectionChangedCommand).To(vm => vm.PersonSelectedCommand);
set.Bind(_source).For(v => v.FetchCommand).To(vm => vm.FetchPeopleCommand);

set.Apply();

While working on the project, I strongly supported the use of this technology, because it provided all the goodies of native UI and at the same time gave us the opportunity to write in our favorite language and reuse a huge amount of code. The speed of development was also not inferior to native development. We used the same classes and models as in native development – Activity, RecyclerView, UIController, UICollectionView, etc. A problem arises, you look for a solution in Java (Kotlin) or Swift and transfer it almost one to one to C#. If the solution is a native library, you make a wrapper for it in an hour and a half and use it. True, with some of them it was not so simple. And to use the same Jetpack Compose or SwiftUI we could not.

A year before the project was closed, the customer decided to do a global redesign of the application. Looking back, I want to say that it was this task that pumped me up in development especially strongly. We developed new libraries with controls for Android and iOS, rethought many old screens and remade much of what was previously written. At the same time, we implemented all the controls from scratch in C# – be it a card, a button or a header that is whitened when the content is scrolled under it.

After the release of .NET 7, we switched from Xamarin to it. Firstly, the new version of MVVM Cross stopped supporting classic Xamarin. Secondly, we wanted to keep up with the times, use the latest version of the language and improve performance due to this transition.

A month before the project was closed, we switched to .NET 8 and were thinking about enabling NativeAot on iOS to make the app open lightning fast. Although even without NativeAot, compared to Android, the iOS app launched much faster.

After the project was closed, I thought that I would continue to push for this approach, which allows writing applications with native UI in one language. There were thoughts about writing my own analogue of MvvmCross, with extensive inclusion of Source Generator for code generation and refusal of reflection wherever possible (DI, serialization, etc. – do not forget about NativeAot, with which the standard serializer does not work).

Xamarin Chapter Summary

Points I liked compared to classic native development:

  • Large shared code base — all business logic, working with databases and the network.

  • One language for development on both platforms. Moreover, I like C# much more than Swift. Therefore, for development exclusively for iOS, I would advise considering the option of using .NET-iOS if you are prejudiced against Swift.

  • Convenient tooling for working with codeextensive refactorings of C# code, user-friendly package manager and .csproj files for project settings. Editing UI files for Android was not very convenient, but I will tell about it in the cons.

  • The usual set of libraries from nuget, which any .NET developer works with.

  • A wide range of performance optimization optionsprovided by the .NET platform – Span, Struct and other things that, in my opinion, allow you to make more productive code even in comparison with the native one.

  • Similar abstractions when working with MVVM for developers who have experience with other C# UI frameworks. If you take MvvmCross as a basis, the approach to ViewModel with INotifyPropertyChanged will be identical.

But there was a lot I didn’t like:

  • Inconvenient tooling when laying out UI. Autocompletion in .xml in Rider worked strangely: showing attributes worked only for basic controls, there were no hints for our controls. However, in Visual Studio this also worked.

  • Collisions when implementing a feature on one platform: common code in the form of models and ViewModels did not fit into the UI of the second platform without additional hacks or reworking the common code.

  • Problems with generics on Android. For example, on our project we had a rule – not to use them for Activity, View and Fragment.

  • Problems with some librarieswhich worked on the old Xamarin, but stopped working after switching to .NET 7. We had to wait until forks of libraries with .NET 7 support appeared.

  • Problems with MvvmCross. It had a number of problems that have not been fixed to this day. Plus, it provides its own DI, which cannot be replaced with its own implementation in any simple way. Hence the problems with the long start of the application, which, however, were partially smoothed out by enabling AoT compilation.

  • Lack of support for writing declarative UIThere is no analogue of Compose and SwiftUI in Xamarin Native.

  • Lack of hot-reload and adequate previewerunlike, for example, MAUI. As a result, the only way to check what happened after editing the markup is to launch the emulator and navigate to the desired screen.

  • Lack of profiler memory to search for leaks specifically in the .NET part of the application.

There were quite a few shortcomings, but I didn’t pay attention to them and really believed in the technology, since the only alternative I tried was the classic native for iOS.

By the way, at that moment I perceived the declarative approach to UI as simply moving the creation of controls from markup files to code and thought: what's all the hype about? Here I am doing the same thing in C#: creating a LinearLayout with TextView and other Views as functions – why not Jetpack Compose. Plus I added more abstractions for more convenient creation in code. And in the end I also did bindings to the created controls, which hung in memory during the entire life cycle of the screen.

If you also think about declarative UI in this way, know this: Compose, Flutter and SwiftUI work differently from classic native, where we create one set of widgets when the screen starts and then mutate their state by changing their properties when something changes on the screen. There is a completely different ideology here, the advantages of which are now absolutely obvious to me.

Example of creating controls from C# code instead of XML
public static View CreateView(Context context)
{
   return new LayoutWrapper<RelativeLayout>(RootView(context))
   {
       new LayoutWrapper<TouchEventLinearLayout>(ContentContainer(context))
       {
           StickerView(context),
           new LayoutWrapper<LinearLayout>(FooterContainer(context))
           {
               new LayoutWrapper<FrameLayout>(StatusContainer(context))
               {
                   ProgressIndicator(context),
                   ErrorImageView(context)
               },
               new LayoutWrapper<FrameLayout>(DateContainer(context))
               {
                   DateTextView(context)
               },
           }
       },
   };
}

Ultimately, the project was frozen for reasons beyond our control. The team of 14 people broke up, half of them went into backend development, and I joined a new project that was decided to be done on Flutter. At the time of the start of work, no one on the team had experience with this framework, so we had to learn right on the fly.

Getting Started with Flutter Development

I would like to point out, so as not to mislead in the future: the previous project and the current one are completely incomparable in terms of volume and complexity.

The previous project was a social network for neighbors with additional services and features. The design of the application reflected the special philosophy of the project, and the designers interacted with the team on a daily basis in both directions, which imposed additional difficulties on the developer associated with achieving pixel-perfect with Figma layouts.

The new project is a service for ordering services with the ability to place an order, view current and active orders, user profile, and news and notification screens.

Development began with choosing the application architecture and defining a gentleman's set of libraries to use.

As a result, after researching popular state managers for Flutter, block as the most understandable for us, C# defectors. For navigation, we chose auto-router. For logging we selected Talkerwhich I noticed in lessons of its authorwhen I watched video tutorials on Flutter. For DI — injectable And retrofit for generating api clients.

Regarding the architecture, it was chosen clean architecture with feature-first application structure. Each application block (screen, section) is a separate feature with its own layers of data, domain and presentation. There is a special plugin for android studio, which immediately creates a hierarchy of folders by the name of the feature. I will not go into detail about clean architecture, I will only note that it made our life much easier during development! Especially after many years of experience working with the layer-first approach to structure, when we have folders Views, ViewModels, Models, Engines, Services, Primitives, etc. at the main level and when developing one screen or a small part of it, we had to run around the entire project in search of the necessary files and classes in which we needed to find this or that logic.

It is clear that it was possible to lay down a feature-first structure earlier, but this approach is rarely found at the level of examples in UI frameworks in C#. Even when creating an empty project, for example, in AvaloniaUI, the project folders are built in the layer-first style. This experience with Flutter convinced me of the need to adhere to a clean architecture with a similar structure in C# projects.

Now I also use clean architecture + feature-first structure for projects on Avalonia

So, we started to lay out the first screens, master bloc, dart, and flutter itself in general. I would like to immediately note the high speed of development. Compared to the native, screens are laid out INSTANTLY, in one go, and even for both platforms at once. I do the layout to get a beautiful screen in accordance with the Figma layout in literally 30 minutes. On XML, such work would take me an hour and a half at best. On iOS, from the experience of my colleagues, it would take even longer. For me, due to my little experience, it is always X2 in time relative to Android.

A bloc is attached to the finished layout, events are passed, and the state is laid. In the block, all the logic is moved to UseCases from the Domain layer, and our presentation layer is ready. In the previous project on Xamarin, there were no UseCases. All business logic was performed in the ViewModel and Engine, which encapsulated the work with the backend, which is now perceived as something wrong without a clear division into layers.

First of all, Flutter has an incredibly convenient hot-reload. Any change is instantly applied on the emulator screen during layout. The same applies to changes in regular methods in your blocks, use-case or repositories. This speeds up development many times over – in comparison with Xamarin. True, MAUI has brought hot-reload, but, as it seems to me, this will not save it from inevitable oblivion.

Secondly, declarative UI is really cool. We use bloc and clean architecture in the Flutter project, and the code is extremely easy to understand. I immediately recall some stubs and stubs that were supposed to be drawn in the native in case of errors or an empty list. As a result, View was covered with a ton of bindings for binding to the Visibility of certain controls on the screen. It was not easy to support this, bugs constantly emerged related to incorrect display of something for each specific situation.

Fortunately, after working with Flutter and the concept of different States for different screen states, you remember the past as a bad dream.

Typical code for hiding/showing controls from a Xamarin Native project
_contentChangesTrigger = new CollectionInitializedTrigger<IEnumerable<Member>>(
   state =>
   {
       switch (state)
       {
           case ContentState.Filled:
               SearchView.Reveal();
               ContentView.Reveal();
               EmptySearchLabel.Hide();
               NoContentView.Hide();
               break;


           case ContentState.Empty:
           case ContentState.FailedToBeFilled:
               AdjustStub();
               bool filterAssigned = !string.IsNullOrEmpty(SearchView.Text);
               SearchView.Hidden = !filterAssigned;
               ContentView.Hide();
               EmptySearchLabel.Hidden = !filterAssigned;
               NoContentView.Hidden = filterAssigned;
               break;
       }
   })
   .LinkTo(Disposer);

Thirdly, I want to talk about Dart. I'll start with the fact that in terms of switching from C#, I'm still confused by the import system. It's annoying that each folder has a file of the same name with an export of all other files in the folder that the plugin should generate, but which doesn't work in the latest versions of the studio. I don't like that when refactoring a class rename, the file name is not renamed. There is also no refactoring – connecting all imports in a file with code. The lack of the same refactoring in the case of kotlin in JetBrains products, by the way, was also an unpleasant discovery for me. Clicking through each place in the code that you copy from somewhere to pull up the necessary imports is not very pleasant after working in Rider with C#, where all this is done in one click.

Also, in Dart, code generation works through a build runner, which is not at all comparable to the same Incremental Source Generators in C#, when the code is generated instantly when writing specific attributes, which determine where and how the code will be generated.

What I got used to right away was the absence of the “new” operator before creating objects. Now, when I return to C#, I constantly forget to write it before the class name. I also liked the presence of sealed classes, which allow you not to use the default value when pattern matching. The enum here has the same behavior. As a result, when extending a type with a new value, we will catch an error at compile time everywhere where we used pattern matching.

Fourth, Flutter has one localization file for the entire projectand there is no official solution for splitting a file into several parts (for example, for each feature) from the Flutter development team yet. Keeping one file with localized strings is not at all convenient even for a small project.

Overall, my impression of Flutter compared to Xamarin is extremely positive. However, Dart and its limitations in multithreading and other nuances described above are confusing. I am also confused by the large number of issues that I still come across during development; there are now more than 12,000 of them in the Flutter repository, and I have added several more in three months of work. The main advantage of Flutter for me is a convenient widget system that allows you to implement screens at incredible speed. Flutter clearly demonstrates that declarative UI is not a tribute to fashion, but an inevitable future. At the moment, Flutter is the best cross-platform framework if you choose it for your new projects. Perhaps in the future, it will be displaced from the pedestal by Kotlin Multiplatform, which, in my opinion, has not yet reached the condition to compete with it.

What's there besides Flutter?

The first thing that comes to mind for a developer who has worked with Xamarin Native for several years is, of course, MAUI – new-old flagship UI framework from Microsoft with the old approach to building interfaces, migrated from Xamarin Forms. To somehow justify the new name, Microsoft announced desktop support for Windows and MacOs and ultimately spent all its efforts to make friends with the old mobile framework with gestures, keyboard support and other things that distinguish the desktop from mobile phones. .NET 9 is already looming, and the MAUI team, from the innovations, talks only about some performance improvements and other things that are not very interesting from the development point of view. Intuition tells me that MAUI will end in a year or two, like Silverlight, WSAUWP, VS for Mac 2022. And that's a shame, given my love for the .NET platform.

In my opinion, independent dream framework for mobile development as follows:

This is an analogue of Flutter with Hot Reload, declarative UI and F# support, which, in my opinion, could fit perfectly into the declarative approach. Perhaps, this would give a second birth to F#, similar to what happened with Dart. We are unlikely to wait for something similar from Microsoft, following MAUI, they will most likely come up with another XAML framework or abandon this niche forever. Therefore, I sincerely hope that there will be enthusiasts, at the level of AvaloniaUI developers, who are capable of making that very dream framework.

While writing this article I decided to try it too Kotlin Multiplatform. I will describe their conclusions regarding this technologywhich can be done in a few days of dating:

  • The split UI approach still looks viable. On Android — Jetpack Compose, on iOS — SwiftUI. And only the domain and data layers are common for Kotlin apps. Having tried to create an app with shared UI (which is still in alpha for iOS), I realized that in this form I would not be able to write an app in the same comfortable mode as when developing on Flutter.

  • The build system based on kotlin.gradle.kts files looks wild. I get lost in the number of files and what's going on inside. The process of adding some libraries after Flutter and .NET also seems inconvenient: you have to copy the lines with the required version from the library's website and paste them into the right places scattered in different .kts files. After pub and nuget managers, this looks like some kind of fierce archaism. Perhaps my opinion will change, but this is my first impression.

  • Kotlin — one loveliterally from the very first code examples from kmp tutorials, and I liked some of the constructions already through the prism of my acquaintance with dart. If I had immediately switched from C# to Kotlin, perhaps I would not have liked it so much.

conclusions

Conclusion 1. No matter how cool a technology seems to you, you don't have to limit yourself to its limitations. Now I am grateful to fate that I was able to break away from the Xamarin Native development that I praised, and which I now do not want to return to.

Conclusion 2. Declarative UI is here to stayso if you haven't tried it yet, I advise you to hurry up. There are thousands of educational videos on YouTube that will help you master absolutely any technology.

Tell us whether you use MAUI or Xamarin Native in production. Did you have any previous experience with declarative frameworks? I invite you to the discussion and remind you of the second part of the story about our transition from my colleague Victor.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *