Апгрейд с Xamarin.Native на .NET 8
Привет, Хабр!
Меня зовут Андрей и я Xamarin разработчик в компании EvApps. Для тех, кто занят в кроссплатформенной мобильной разработке на Xamarin не секрет, что Microsoft прекратил поддержку этой платформы и выкатил обновленную реализацию на чистом .NET. Речь о .NET Android, .NET iOS, MAUI и т.д. Поначалу мне казалось, ну сделали и сделали, что такого может случиться, но как это бывает беда всё же пришла.
Пришла она в виде письма от команды Google Play с предупреждением о том, что наше приложение должно быть предназначено для Android 14 (API уровня 34+) или мы не сможем публиковать обновления. Тест письма привести не могу, но вот скриншот из консоли разработчика.
Предупреждение от Google Play
А Xamarin поддерживает максимум Android 13. Что делать? Конечно, срочно изучать Flutter мигрировать на .NET!
В этой статье я приведу ссылку на руководство по миграции проектов Xamarin.Native на .NET и расскажу о проблемах, с которыми столкнулся и о том, как я их решал.
1. Про IDE
Прежде чем я расскажу непосредственно о процессе миграции, немножко еще пожалуюсь на Microsoft несколько слов про IDE, поскольку некоторые проблемы, с которыми я столкнулся, от этого зависят.
Поскольку я разрабатываю приложения под Android и iOS, то работаю на маке. За время работы в компании очень привык к Visual Studio for Mac, с ней и работал, пока Microsoft не сказали, что ее отправляют на пенсию в августе 2024 года. Однако, на момент возникновения необходимости миграции этого еще не произошло, и я совершенно не переживал. Пока не обнаружилось, что VS for Mac не поддерживает .NET 8 (это потом я узнал, что, оказывается, нужно включить поддержку .NET 8 в блоке настроек Preview Features, чтобы это преодолеть), что побудило меня сменить IDE.
Включение поддержки .NET 8 в VS for Mac
Я честно попробовал предлагаемый Microsoft VS Code + C# Dev Kit, но оказалось, что нужно еще как-то самому подтянуть в него Android SDK, а в отличие от добренькой Visual Studio, VS Code не собирается водить за ручку и рассказывать куда тебе нужно ткнуть (хотя может быть это я не разобрался, но время на это я тратить не хотел, напишите о своем опыте в комментариях). В связи с этим мой выбор пал на JetBrains Rider (версия на момент миграции — 2024.1.4). С ним я немного знаком (использовал его в процессе знакомства с Unity), мне он кажется удобным (и во многом это действительно так), и работает он на маке шустрее чем студия. Как показала практика, с Rider тоже есть проблемы, но об этом дальше.
2. Про Microsoft Learn
В первую очередь я решил посмотреть в базе знаний Microsoft нет ли там руководства и оказалось, что оно есть.
Прочитав его, я убедился, что процесс обещает быть несложным. Если расписать его по шагам, то получится вот что:
Создать новый проект под каждую платформу (в моем случае проект под iOS, проект под Android и .NetStandart проект);
Скопировать имеющийся код проекта, порешать посыпавшиеся нэйминги, убрать использование откровенно устаревших API (например, Android.Support.V4.App), поменять в .csproj файлах устаревшие директивы, перенести зависимости в новый проект;
Обновить зависимости. Вот тут есть небольшое требование — библиотеки должны поддерживать .NET 8;
Готово! Вы прекрасны!
3. Создание нового проекта
Казалось бы, что тут может пойти не так? Однако Rider подкинул мне небольшую свинью. Проекты под Android и .NetStandart создались без проблем, а вот варианта создания iOS проекта не было. Напомню, версия Rider у меня 2024.1.4, и, возможно, в будущих версиях это поправят.
Нет опции создания iOS проекта в Rider
Решение оказалось простым — рядом с полем выбора TargetFramework есть возможность выбрать версию .NET SDK с которой будем работать, нужно выбрать версию SDK 9.0, TargetFramework net8.0 и опция создания iOS проекта появляется.
Поменяли SDK, и опция появилась
4. Перенос кода
Как уже говорилось выше, под переносом кода имеется ввиду копирование файлов из старых (Xamarin.Native) проектов в только что созданные новые. При этом необходимо добавить все нужные зависимости и разрешить возникающие проблемы, такие как использование устаревших API, некорректные пространства имен и вообще все, что IDE подсветит как проблему. Наверняка могут возникнуть ошибки, связанные с обновлением зависимостей, но об этом дальше.
В моем случае, перенос кода прошел без особых проблем, за исключением пары моментов:
Скорее всего это фишка Rider, а конкретно ReSharper, но в андроид проекте IDE периодически путается откуда тащить ресурсы. Объявление типа Resource.String.yourStringResource или Resource.Layout.yourLayoutResource вызывают у Rider’а смущение, если в проекте есть библиотека, в которой тоже есть ресурсы (в моем случае это была библиотека Acr.UserDialogs). Rider не может сообразить откуда конкретно вытаскивать значения — объявляем напрямую using Resource =
.Resource и IDE успокаивается; Если в коде использовалась библиотека Xamarin.Essentials, например, для обработки запросов разрешений, ее следует заменить на Microsoft.Maui.Essentials и в файлах .csproj проектов добавить директиву UseMauiEssentials со значением true. После этого, так же как и для Xamarin.Essentials ее необходимо инициализировать с помощью метода Platform.Init. Если этого не сделать, в момент обращения к функционалу библиотеки может возникать ошибка. Конкретно я с этим не столкнулся, но, если вы с этим сталкивались и решали проблему как-то иначе, напишите в комментариях;
Вот это совершенно точно проблема Rider. Всем, кто разрабатывает что-то под iOS известно, что для тестирования приложения нужен сертификат разработчика и provision profile. Где и как их раздобыть также не секрет. Я столкнулся с тем, что Rider 2024.1.4 не видит provision profile, связанный с установленными сертификатами и BundleID приложения. Если же прописать его непосредственно в .csproj файле, Rider отображает его название, но помечает как missing.
Rider не может найти provision profile
Provision profile указан в .csproj, но Rider его не «видит»
К счастью, JetBrains в курсе проблемы и работают над ее устранением. На момент написания статьи фикс уже был, но только в версии для раннего доступа — Rider EAP 7+. Возможно, когда вы будете читать эту статью, исправление уже будет и в «боевой».
5. Обновление зависимостей
Прежде чем обновлять зависимости в проекте, согласно гайду, необходимо убедиться, что библиотека поддерживает .NET 8. Для этого нужно проверить поддерживаемые фреймворки для каждой библиотеки на сайте nuget.org. Попереключаться между версиями, проверить вкладку Frameworks и убедиться, что нужный отмечен бледно- или темно-синим фоном. В первом случае указанный фреймворк совместим с библиотекой, во втором — включен в нее.
Вкладка Frameworks на nuget.org
Однако, тут я столкнулся с такой проблемой, что не всегда написанное соответствует действительности. Некоторые опенсорсные библиотеки вроде как должны поддерживать net8.0-android и net8.0-ios, но на деле при попытке к ним обратиться выдают PlatformNotSupportedException (в моем случае) или какую-то иную ошибку. У меня такое произошло с библиотеками PCLCrypto и ZXing.Net.Mobile при работе с iOS проектом. Зачастую проблема решается открытием проблемы в репозитории с исходным кодом библиотеки и некоторым временем ожидания фикса. Но что, если автор библиотеки ее забросил и более не поддерживает?
При прекращении поддержки каждый автор ведет себя по-разному. Кто-то просто молча забрасывает проект, а кто-то (и большое спасибо таким людям) убирает его «в архив» и при этом описывает возможные альтернативы более неподдерживаемой библиотеке.
Например, создатель библиотеки PCLCrypto в описании на GitHub сообщил, что библиотека разрабатывалась, чтобы компенсировать отсутствие в .NET Runtime криптографии, имеющейся в .NET Framework, а теперь, поскольку ситуация исправилась, в библиотеке нет смысла и можно использовать .NET криптографию.
С библиотекой ZXing вышло совсем иначе. Автор забросил ее довольно давно (около 3 лет назад) и начал разработку версии под MAUI, а поскольку мой проект не на нем, эта версия мне не подходила. Единственное адекватное решение, к которому я пришел — взять исходный код и по принципу миграции Xamarin.Native проектов переписать ее с учетом поддержки .NET 8. Тут я тоже столкнулся с некоторыми проблемами, но это тема для другой статьи.
После обновления зависимостей остается только попробовать собрать проект, чтобы убедиться, что все работает и никаких ошибок при сборке нет. Сборку стоит запустить как в Debug, так и в Releaseконфигурации. Если какие-то ошибки при сборке возникают, их, разумеется, нужно исправить.
6. Создание сборок для публикации
После миграции кода, обновления зависимостей и исправления проблем, мне оставался последний шаг — сделать сборки для отправки в сторы.
Тут Rider меня приятно удивил. При выборе опции Publish в выпадающем меню проекта он предлагает опубликовать сборку в локальную папку, после чего открывает окно, где можно выбрать целевую папку, настройки для публикации, название, отображаемое в списке конфигураций сборки и т.д.
Окно конфигурации публикации Android-проекта
После выбора всех нужных опций и нажатия Apply конфигурация сохраняется и ее можно выбрать из выпадающего списка конфигураций сборки. Если выбрать Run, то конфигурация не только сохранится, но и сразу запустится процесс создания файла сборки с указанными параметрами.
Выбор конфигураций сборки
Причем, даже если текущая конфигурация, например Debug | Any CPU, как на скриншоте выше, при сборке будут использоваться именно те настройки, которые указаны в настройках сборки для публикации.
Однако, и тут я нашел себе проблем. При попытке опубликовать проект на iOS я получил вот такое сообщение:
«Error NETSDK1124: для усечения сборок требуется .NET Core 3.0 или более поздняя версия». // не знаю есть ли возможность оформить это как-то на каком-то фоне и без кавычек, но если есть, то лучше сделать
Поиск способов исправления ошибки привел меня к рекомендации добавить в .csproj проекта директиву PublishTrimmed со значением false. Это должно отключить усечение сборок и решить проблему, однако, этого не произошло. Ошибка повторялась снова и снова, хотя при запуске проекта без публикации такого не происходило. Через некоторое время меня посетило откровение — у меня же в проекте с shared кодом TargetFramwork — netstandart2.1! Я поменял его на net8.0 и проблема решилась! Да, ошибка возникала исключительно из-за моей невнимательности, я пытался исправить ее не там, где было нужно, но, с другой стороны и сообщение не очень информативное, возможно, в будущем это исправят.
7. Готово! Вы прекрасны!
Ура! Поздравляем!
Код мигрирован, сборки сделаны, публикация запущена и можно выдохнуть! В целом, весь процесс миграции не доставляет особых неудобств, как видите, большинство проблем, с которыми я столкнулся, так или иначе связаны с IDE или собственной невнимательностью. Сильную боль могут доставить только библиотеки, но это тоже вопрос решаемый, просто требует времени и определенных усилий со стороны разработчика.
Большое спасибо за внимание, надеюсь кому-то эти мои откровения окажутся полезны. Если у Вас есть вопросы, предложения, критика или хотите поделиться собственным опытом миграции с Xamarin на .NET — добро пожаловать в комментарии)