Migrating & Updating old Xamarin Native MvvmCross projects
6 Steps taken to perform the migration, and some hindsight
Xamarin Native was extremely popular as a cross platform solution, and MVVMCross the most popular framework used with it, primarily due to it’s reliability and robust capabilities. Fortunately, Xamarin Native and MVVMCross both get frequent updates, but most companies don’t regularly update their apps or app frameworks because they didn’t need to. There’s still a ton of apps still out there written with Xamarin Native & MVVMCross, as is evident from the MvvmCross nuget package still getting ~1.5k downloads a day. So I hope this acts as a guide for anyone looking to migrate to the latest frameworks! Be sure you check this article before determining whether to actually perform a migration for your app.
For people not using MvvmCross, but trying to migrate to AndroidX, you can skip to Step 5 and it might be useful. And if you are looking to learn Xamarin Native so you can be uber-attractive for the 10% of published Xamarin Apps still using Xamarin Native (The First Prototype’s speciality), you can look at this mini-guide.
A tale of three migrations
The Xamarin solution we migrated was created in 2017, so it was using PCL instead of .NET Standard for the two Core projects-one for Services and the other for ViewModels. Most of the solution’s dependencies did not support PCL anymore in it’s latest versions and required .NET Standard 2.0. So apart from updating the MVVMCross framework from 4.4 to 7.1.2, we would be making that change as well. We had a choice to stop there, but I took the challenge and decided to migrate to the latest AndroidX libraries as well.
Methodology: I did a quick spike to see if I’d just be able to switch everything out using just the existing solution, but I was having too many issues, there were too many changes and I eventually reached a roadblock. So it seemed much smarter to start from a fresh .NET Standard Xamarin Native solution and add one project to it at a time. I decided to start with the core projects (ViewModels & Services), then iOS & then Android (since Android requires the AndroidX migration as well).
Tooling: I started off using Visual Studio, but Jetbrains Rider is better for migrations because it calculates the total number of build errors and updates it on the fly without having to press rebuild everytime. Strangely, when I had >15000 build errors, Visual Studio only showed ~50. Also on Rider, with one click you can magically add a missing using in all the files of your project and save a ton of time.
Source Control: I created a new repository for this, since I knew it would take me several days, and I want to be able to save my work as I go. I would only commit and push a project once I was able to build. Once I made all the changes, I deleted all the old files from the old repository and pasted the new code in it’s place
Step 1 — Basic MVVMCross Xamarin Native project
- Create a New Solution using the Blank Xamarin Native template. Make sure it has the exact same name.
- Rename the Core project to your old project name, so
.ViewModelsin my case. Update theSLNandCSPROJfiles so that they also point to the renamedViewModelsproject - Go to the
ViewModelsproject options and update the Project Options->Target Framework to.NetStandard2.0. (Migrating to .NETStandard2.1 gave me some dependency issues).
Note: Since you make the change from PCL to .NETStandard, you won’t be needingproject.json,app.config,AssemblyInfo.csfiles, and theCSProjcan just automatically include everything in folders without needing to specify each file. - Add the latest MvvmCross (7.1.2) and make sure you are able to build iOS and Android with the changes requested by MvvmCross to the ViewModel and UI Projects. I implemented the changes from the MvvmCross TipCalc repo, and made sure the app builds on both platforms (iOS & Android)
Step 2 — Migrating the Core ViewModels and Services Projects
- Right click on the ViewModel solution and select “Add->Files from Folder” and add select everything from the ViewModels directory of the old solution using the existing folder structure.
- Add the needed Nuget Packages like ACR Settings, AppCenter, MvvmCross, MvvmCross Plugins-Email/Messenger/Phone/WebBrowser, MvvmValidation, Plugin MediaManager, Newtonsoft.Json, Xamarin essentials. Xamarin Essentials can prevent the need of some other plugins like Connectivity and DeviceInfo
- Add the second Core project-Services(if needed) and make sure it has the right name, Target framework, and make sure the ViewModel project uses it as a reference.
- To the Services project, add the needed Nuget Packages (ACR Settings, AppCenter, MvvmCross, MvvmCross Messenger, Newtonsoft.Json)
- Remember to make the change from PCL to .NETStandard 2.0. Again, you won’t be needing project.json, app.config, AssemblyInfo.cs files, and the CSProj can just automatically include everything in folders without needing to specify each file. So you can just right click and “Add->Files from Folder” to add all the remaining files from the older Services directory.
- If using the older `Plugin.Connectivity` anywhere, you can convert
CrossConnectivity.Current.IsConnectedtoXamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.Internet - Replace
using MvvmCross.Platformwith justusing MvvmCross - Replace
using MvvmCross.Binding.ExtensionMethodswithusing MvvmCross.Binding.Extensions - Replace
using MvvmCross.Plugins.Messengerwithusing MvvmCross.Plugin.Messenger - Replace
Settings.Current.xwithCrossSettings.Current.x - Replace
using MvvmCross.Core.ViewModelstousing MvvmCross.Commandsorusing MvvmCross.ViewModelsas needed.
Step 3 — Some specific Core project errors:
- Most likely you will have errors in the Navigation because of the riddance of some functions(Close, ShowViewModel) to start using Navigator.Close and Navigator.Navigate
- The way data is passed into a view model changed because of the lack of usage of “MvxBundle”, to instead start using Parameters which require the Prepare lifecycle function override.
- All the IMvxCommands and MvxCommands will need a
using MvvmCross.Commandsin the file or just addMvvmCross.Commands.before every single place it is mentions. Rider’s functionality to add all the required using statements can resolve this in the best way. - One of my errors said that I needed to add the CSharp DLL, so I added the C# nuget and it resolved the error.
- Any pages using media is a lot more work, because the events have changed, and some properties are unavailable. All Status suffixes have changed to State. I left some of this to be fixed after I was done with the rest of the migration, so I could work on it while the QA team was testing the remaining work.
Mvx.Tracestopped working so I just switched to useDebug.WriteLine. And for anything else I wasn’t sure of, I would comment the code along with a//TODO: Fix latercomment.- Once done with all 5000+ errors and you are able to build the core project, you will not be able to build on iOS yet because you will need to add the Nuget Packages there (ACR Settings, AppCenter, MvvmCross, MvvmCross Plugins-Email/Messenger/Phone/WebBrowser, Newtonsoft Json, MvvmValidation, Plugin Mediamanager, Xamarin Essentials).
- Some ViewModels may also take longer to fix depending on how information was passed when we got to the page
Step 4 — iOS Project change:
- Took the older files from the older directly, using “Add->Files from Folder” similar to the step mentioned above.
- Replace
using MvvmCross.Binding.iOS.Viewswithusing MvvmCross.Platforms.Ios.Binding.Views - Replace
using MvvmCross.Core.ViewModelstousing MvvmCross.Commandsorusing MvvmCross.ViewModelsas needed. - Replace
using MvvmCross.iOS.Support.Viewswithusing MvvmCross.Platforms.Ios.Views - Replace
using MvvmCross.Platform.Converterswithusing MvvmCross.Converters - Replace
using MvvmCross.Binding.ExtensionMethodswithusing MvvmCross.Binding.Extensions` - Removed
using MvvmCross.Platform.Coreand added the missing usings with the help of Rider:MvvmCross.Binding.BindingContextfor IMvxBindingContext,MvvmCross.ViewModelsfor IMvxInteraction, andMvvmCross.Basefor MvxValueEventArgs & IMvxDataConsumer - Replace
using MvvmCross.Platformtousing MvvmCross - Replace
using MvvmCross.Core.Navigationwithusing MvvmCross.Navigation - Removed the Bootstrap files since its not needed anymore, and remove references to it
- Updated the delicate
AppDelegatefile to work with the updatedAppStart. Also updated theSetup.csfile as needed - Updated the DebugTrace.cs to inherit from IMvxLog instead of IMvxTrace and fixed it up
- Since we are not using the older
Plugin.Connectivityanywhere, convertCrossConnectivity.Current.IsConnectedtoXamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.Internet - Luckily, no changes on the media recording player were needed on the iOS UI side except for the using statements.
- If you are using a custom iOS view presenter (using a class that inherits from
MvxIosViewPresenter), you might be in for a hard time. Especially if you are using the `view` from this override:Show(IMvxIosView view, MvxViewModelRequest request), because that override is not available anymore. You only have access toShow(MvxViewModelRequest request). I was able to use this new override insteadCreateOverridePresentationAttributeViewInstance(Type viewType)by just sayingvar view = base.CreateOverridePresentationAttributeViewInstance(viewType);to update attributes on the UIViewController - You might also have section header expand issues with TableSources inheriting from MvxExpandableTableViewSource. Upon debugging I realized, MvvmCross adds a hidden button automatically, that knows to tap to expand. I noticed that this class was using a container UIView to put labels and images inside. Upon removing the container, it started working again.
- Some of your TableViewSources might be using a variation of
ItemsSource.ElementAt(i).IsExpanded = false;which is not available anymore. You can fix it by using this insteadvar group = (ItemsSource as IList)[i] as DocumentListGroup; if (group != null) group.IsExpanded = false;
Step 5 — MvvmCross update & Android->AndroidX
- Took the files using the same file and folder structure using the “Add->Files from Folder”. Then, just add the missing using statements and you’ll have some of the issues we saw from the iOS steps above.
- Replace `MvvmCross.Binding.Droid.Target;` with `using AndroidX.AppCompat.Widget`
- Replace
using Android.Appandusing Android.Widgetwithusing AndroidX.AppCompat.Widget. But for the[ActivityAttribute, you can convert those to[Android.App.Activity - Replace
using MvvmCross.Droid.Support.V7.AppCompatwith usingMvvmCross.Platforms.Android.Views.AppCompat - Replace
using MvvmCross.Droid.Viewswithusing MvvmCross.Views - Replace
Android.Support.V4.Content.Reswithusing AndroidX.Core.Content.Resources - Replace
Application.Context.xwithCrossCurrentActivity.Current.AppContext.xusing the Plugin.CurrentActivity in several places - Replace
using Toolbar = Android.Support.V7.Widget.Toolbartousing Toolbar = AndroidX.AppCompat.Widget.Toolbar - Replace
using Android.Support.V4.Widgetandusing Android.Support.V4.Viewas needed withusing AndroidX.Core.View,using AndroidX.DrawerLayout.Widgetorusing MvvmCross.Platforms.Android.Views - Replace
using MvvmCross.Core.ViewModelstousing MvvmCross.Commandsorusing MvvmCross.ViewModelsas needed. - Replace
using MvvmCross.Platform.Converterswithusing MvvmCross.Converters - Replace
using MvvmCross.Binding.ExtensionMethodswithusing MvvmCross.Binding.Extensions - Removed
using MvvmCross.Platform.Coreand addedusing MvvmCross.Binding.BindingContextfor IMvxBindingContext,using MvvmCross.ViewModelsfor IMvxInteraction, andusing MvvmCross.Basefor MvxValueEventArgs & IMvxDataConsumer - Replace
using MvvmCross.Platformtousing MvvmCrossfor Mvx.Resolve - Replace
using MvvmCross.Core.Navigationtousing MvvmCross.Navigation - Replace
using Plugin.MediaManager.ExoPlayer&using Plugin.MediaManagerwithusing MediaManager - Replace
using MvvmCross.Binding.Droid.BindingContextwithusing MvvmCross.Platforms.Android.Binding.BindingContext - Replace
using MvvmCross.Binding.Droid.Viewswithusing MvvmCross.Platforms.Android.Binding.Views - Replace
using MvvmCross.Droid.Support.V7.RecyclerViewwithusing MvvmCross.DroidX.RecyclerViewfor which you need to install the MvvmCross.DroidX.RecyclerView nuget - Replace
using MvvmCross.Droid.Views.Attributeswithusing MvvmCross.Platforms.Android.Presenters.Attributes - Replace
using MvvmCross.Droid.Support.V4withusing MvvmCross.Platforms.Android.Views.Fragmentsfor Fragments or sometimes withusing MvvmCross.DroidX - Most of my activities required adding
using AndroidX.Lifecycle - I also needed to add
using Google.Android.Material.BottomSheetandusing Google.Android.Material.Snackbarin some classes - As a personal choice, I converted the
MvxAppCompatSpinnertoMvxSpinner, and that layout also needed fixing - Tabbed pages can be a bit challenging. You need to udate the
MvxViewPagerFragmentInfo, since it now requires passing aMvxViewModelRequestinstead of just theViewModel. Passing in parameters is a little different now. I was kind of stuck there, looking at the docs. I found an example and a stackoverflow answer but it didn’t help much. Thanks to the MvvmCross Tomasz’s tip, I found thatMvxViewModelInstanceRequestis a type ofMvxViewModelRequestwhich will allow you to pass in the ViewModel instance and that makes it a breeze. - Some of the tabbed pages might still break and might give you an
System.Reflection.AmbiguousMatchFoundexception crash, and finding the source of the crash can be difficult - Removed the Bootstrap files since its not needed anymore, and remove references to it
- Converting the startup to the new style
- Converting the log in debugtrace
- Since we are using the older `Plugin.Connectivity` anywhere, convert `CrossConnectivity.Current.IsConnected` to `Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.Internet
- More than 70 Fragments, Activities and Layout file changes, but I stopped counting a long time ago. Updated the custom WebView Client
- Quite a few changes on audio media player were needed on the Android UI side
- Update all the dialogs including the AlertDialog which needed quite some work because of the AndroidX changes. Convert the classes that inherit from
DialogFragmentto start inheriting fromMvxDialoginstead. Now thedialog.Show()will need aSupportFragmentManagerinstead. This step might cause you some tricky icky issues. For your dialogs, if you are using EnsureBindingContextSet(savedState), you can use this instead this.EnsureBindingContextIsSet();
Step 6 — Non Csharp Android changes-layout & menu xml files
android.support.v4.widget.DrawerLayouttoandroidx.drawerlayout.widget.DrawerLayoutandroid.support.v4.view.ViewPagertoandroidx.viewpager.widget.ViewPagerandroid.support.v4.widget.NestedScrollViewtoandroidx.core.widget.NestedScrollViewMvvmCross.Droid.Support.V4.MvxSwipeRefreshLayouttoMvvmCross.DroidX.MvxSwipeRefreshLayoutMvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerViewtoMvvmCross.DroidX.RecyclerView.MvxRecyclerViewandroid.support.v7.widget.AppCompatButtontoandroidx.appcompat.widget.AppCompatButtonandroid.support.v7.widget.SwitchCompattoandroidx.appcompat.widget.SwitchCompatandroid.support.v7.widget.SearchViewtoSearchViewandroid.support.v7.widget.Toolbartoandroidx.appcompat.widget.Toolbarandroid.support.v7.widget.CardViewtoandroidx.cardview.widget.CardViewandroid.support.design.widget.CoordinatorLayouttoandroidx.coordinatorlayout.widget.CoordinatorLayoutandroid.support.design.widget.AppBarLayouttocom.google.android.material.appbar.AppBarLayoutandroid.support.design.widget.TextInputLayouttocom.google.android.material.textfield.TextInputLayoutandroid.support.design.widget.TextInputEditTexttocom.google.android.material.textfield.TextInputEditTextandroid.support.design.widget.TabLayouttocom.google.android.material.tabs.TabLayoutandroid.support.design.widget.FloatingActionButtontocom.google.android.material.floatingactionbutton.FloatingActionButtonandroid.support.design.widget.NavigationViewtocom.google.android.material.navigation.NavigationView- layout_behavior=
android.support.design.widget.BottomSheetBehaviortocom.google.android.material.bottomsheet.BottomSheetBehavior - Make sure you target the latest framework, and can download and upload files correctly. You can temporarily fix issues by enabling the legacy app storage permissions when targeting framework Android 10. Updating it to the new APIs is not too difficult.
- A strange one is if you are using
MvxFrameControl, you might have some issues. You have to switch it fromMvvmCross.Binding.Droid.Views.MvxFrameControltoMvvmCross.Platforms.Android.Binding.Views.MvxFrameControl - In any menus, if you are using actionViewClass=“
SearchView”, change that value toandroidx.appcompat.widget.SearchView. - This one wasn’t the hardest to figure out, I had to do some google searches. Alternatively, you can also take a look at this little guide.
There were other issues as well that I may not have documented through the process, but I hope this gives you and your team a good starting point to understand the work and complexity involved in the migration process. As always, feel free to reach out to me if you have any questions. Don’t forget to consult the migration guides provided by MvvmCross and StackOverflow for any roadblocks
