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
.ViewModels
in my case. Update theSLN
andCSPROJ
files so that they also point to the renamedViewModels
project - Go to the
ViewModels
project 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.cs
files, and theCSProj
can 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.IsConnected
toXamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.Internet
- Replace
using MvvmCross.Platform
with justusing MvvmCross
- Replace
using MvvmCross.Binding.ExtensionMethods
withusing MvvmCross.Binding.Extensions
- Replace
using MvvmCross.Plugins.Messenger
withusing MvvmCross.Plugin.Messenger
- Replace
Settings.Current.x
withCrossSettings.Current.x
- Replace
using MvvmCross.Core.ViewModels
tousing MvvmCross.Commands
orusing MvvmCross.ViewModels
as 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.Commands
in 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.Trace
stopped 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 later
comment.- 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.Views
withusing MvvmCross.Platforms.Ios.Binding.Views
- Replace
using MvvmCross.Core.ViewModels
tousing MvvmCross.Commands
orusing MvvmCross.ViewModels
as needed. - Replace
using MvvmCross.iOS.Support.Views
withusing MvvmCross.Platforms.Ios.Views
- Replace
using MvvmCross.Platform.Converters
withusing MvvmCross.Converters
- Replace
using MvvmCross.Binding.ExtensionMethods
withusing MvvmCross.Binding.Extensions
` - Removed
using MvvmCross.Platform.Core
and added the missing usings with the help of Rider:MvvmCross.Binding.BindingContext
for IMvxBindingContext,MvvmCross.ViewModels
for IMvxInteraction, andMvvmCross.Base
for MvxValueEventArgs & IMvxDataConsumer - Replace
using MvvmCross.Platform
tousing MvvmCross
- Replace
using MvvmCross.Core.Navigation
withusing MvvmCross.Navigation
- Removed the Bootstrap files since its not needed anymore, and remove references to it
- Updated the delicate
AppDelegate
file to work with the updatedAppStart
. Also updated theSetup.cs
file 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.Connectivity
anywhere, convertCrossConnectivity.Current.IsConnected
toXamarin.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.App
andusing Android.Widget
withusing AndroidX.AppCompat.Widget
. But for the[Activity
Attribute, you can convert those to[Android.App.Activity
- Replace
using MvvmCross.Droid.Support.V7.AppCompat
with usingMvvmCross.Platforms.Android.Views.AppCompat
- Replace
using MvvmCross.Droid.Views
withusing MvvmCross.Views
- Replace
Android.Support.V4.Content.Res
withusing AndroidX.Core.Content.Resources
- Replace
Application.Context.x
withCrossCurrentActivity.Current.AppContext.x
using the Plugin.CurrentActivity in several places - Replace
using Toolbar = Android.Support.V7.Widget.Toolbar
tousing Toolbar = AndroidX.AppCompat.Widget.Toolbar
- Replace
using Android.Support.V4.Widget
andusing Android.Support.V4.View
as needed withusing AndroidX.Core.View
,using AndroidX.DrawerLayout.Widget
orusing MvvmCross.Platforms.Android.Views
- Replace
using MvvmCross.Core.ViewModels
tousing MvvmCross.Commands
orusing MvvmCross.ViewModels
as needed. - Replace
using MvvmCross.Platform.Converters
withusing MvvmCross.Converters
- Replace
using MvvmCross.Binding.ExtensionMethods
withusing MvvmCross.Binding.Extensions
- Removed
using MvvmCross.Platform.Core
and addedusing MvvmCross.Binding.BindingContext
for IMvxBindingContext,using MvvmCross.ViewModels
for IMvxInteraction, andusing MvvmCross.Base
for MvxValueEventArgs & IMvxDataConsumer - Replace
using MvvmCross.Platform
tousing MvvmCross
for Mvx.Resolve - Replace
using MvvmCross.Core.Navigation
tousing MvvmCross.Navigation
- Replace
using Plugin.MediaManager.ExoPlayer
&using Plugin.MediaManager
withusing MediaManager
- Replace
using MvvmCross.Binding.Droid.BindingContext
withusing MvvmCross.Platforms.Android.Binding.BindingContext
- Replace
using MvvmCross.Binding.Droid.Views
withusing MvvmCross.Platforms.Android.Binding.Views
- Replace
using MvvmCross.Droid.Support.V7.RecyclerView
withusing MvvmCross.DroidX.RecyclerView
for which you need to install the MvvmCross.DroidX.RecyclerView nuget - Replace
using MvvmCross.Droid.Views.Attributes
withusing MvvmCross.Platforms.Android.Presenters.Attributes
- Replace
using MvvmCross.Droid.Support.V4
withusing MvvmCross.Platforms.Android.Views.Fragments
for Fragments or sometimes withusing MvvmCross.DroidX
- Most of my activities required adding
using AndroidX.Lifecycle
- I also needed to add
using Google.Android.Material.BottomSheet
andusing Google.Android.Material.Snackbar
in some classes - As a personal choice, I converted the
MvxAppCompatSpinner
toMvxSpinner
, and that layout also needed fixing - Tabbed pages can be a bit challenging. You need to udate the
MvxViewPagerFragmentInfo
, since it now requires passing aMvxViewModelRequest
instead 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 thatMvxViewModelInstanceRequest
is a type ofMvxViewModelRequest
which 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.AmbiguousMatchFound
exception 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
DialogFragment
to start inheriting fromMvxDialog
instead. Now thedialog.Show()
will need aSupportFragmentManager
instead. 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.DrawerLayout
toandroidx.drawerlayout.widget.DrawerLayout
android.support.v4.view.ViewPager
toandroidx.viewpager.widget.ViewPager
android.support.v4.widget.NestedScrollView
toandroidx.core.widget.NestedScrollView
MvvmCross.Droid.Support.V4.MvxSwipeRefreshLayout
toMvvmCross.DroidX.MvxSwipeRefreshLayout
MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
toMvvmCross.DroidX.RecyclerView.MvxRecyclerView
android.support.v7.widget.AppCompatButton
toandroidx.appcompat.widget.AppCompatButton
android.support.v7.widget.SwitchCompat
toandroidx.appcompat.widget.SwitchCompat
android.support.v7.widget.SearchView
toSearchView
android.support.v7.widget.Toolbar
toandroidx.appcompat.widget.Toolbar
android.support.v7.widget.CardView
toandroidx.cardview.widget.CardView
android.support.design.widget.CoordinatorLayout
toandroidx.coordinatorlayout.widget.CoordinatorLayout
android.support.design.widget.AppBarLayout
tocom.google.android.material.appbar.AppBarLayout
android.support.design.widget.TextInputLayout
tocom.google.android.material.textfield.TextInputLayout
android.support.design.widget.TextInputEditText
tocom.google.android.material.textfield.TextInputEditText
android.support.design.widget.TabLayout
tocom.google.android.material.tabs.TabLayout
android.support.design.widget.FloatingActionButton
tocom.google.android.material.floatingactionbutton.FloatingActionButton
android.support.design.widget.NavigationView
tocom.google.android.material.navigation.NavigationView
- layout_behavior=
android.support.design.widget.BottomSheetBehavior
tocom.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.MvxFrameControl
toMvvmCross.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