Привет, Хабр!

Меня зовут Андрей и я Xamarin разработчик в компании EvApps. Для тех, кто занят в кроссплатформенной мобильной разработке на Xamarin не секрет, что Microsoft прекратил поддержку этой платформы и выкатил обновленную реализацию на чистом .NET. Речь о .NET Android, .NET iOS, MAUI и т.д. Поначалу мне казалось, ну сделали и сделали, что такого может случиться, но как это бывает беда всё же пришла.

Пришла она в виде письма от команды Google Play с предупреждением о том, что наше приложение должно быть предназначено для Android 14 (API уровня 34+) или мы не сможем публиковать обновления. Текст письма привести не могу, но вот скриншот из консоли разработчика.

Предупреждение от Google Play
Предупреждение от 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
Включение поддержки .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
Нет опции создания iOS проекта в Rider

Решение оказалось простым - рядом с полем выбора TargetFramework есть возможность выбрать версию .NET SDK с которой будем работать, нужно выбрать версию SDK 9.0, TargetFramework net8.0 и опция создания iOS проекта появляется.

Поменяли SDK, и опция появилась
Поменяли SDK, и опция появилась

4. Перенос кода

Как уже говорилось выше, под переносом кода имеется ввиду копирование файлов из старых (Xamarin.Native) проектов в только что созданные новые. При этом необходимо добавить все нужные зависимости и разрешить возникающие проблемы, такие как использование устаревших API, некорректные пространства имен и вообще все, что IDE подсветит как проблему. Наверняка могут возникнуть ошибки, связанные с обновлением зависимостей, но об этом дальше.

В моем случае, перенос кода прошел без особых проблем, за исключением пары моментов:

  • Скорее всего это фишка Rider, а конкретно ReSharper, но в андроид проекте IDE периодически путается откуда тащить ресурсы. Объявление типа Resource.String.yourStringResource или Resource.Layout.yourLayoutResource вызывают у Rider'а смущение, если в проекте есть библиотека, в которой тоже есть ресурсы (в моем случае это была библиотека Acr.UserDialogs). Rider не может сообразить откуда конкретно вытаскивать значения - объявляем напрямую using Resource = <your project>.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
Rider не может найти provision profile
Provision profile указан в .csproj, но Rider его не «видит»
Provision profile указан в .csproj, но Rider его не «видит»

К счастью, JetBrains в курсе проблемы и работают над ее устранением. На момент написания статьи фикс уже был, но только в версии для раннего доступа - Rider EAP 7+. Возможно, когда вы будете читать эту статью, исправление уже будет и в «боевой».

5. Обновление зависимостей

Прежде чем обновлять зависимости в проекте, согласно гайду, необходимо убедиться, что библиотека поддерживает .NET 8. Для этого нужно проверить поддерживаемые фреймворки для каждой библиотеки на сайте nuget.org. Попереключаться между версиями, проверить вкладку Frameworks и убедиться, что нужный отмечен бледно- или темно-синим фоном. В первом случае указанный фреймворк совместим с библиотекой, во втором - включен в нее.

Вкладка Frameworks на nuget.org
Вкладка 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-проекта
Окно конфигурации публикации 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 - добро пожаловать в комментарии)

Комментарии (1)


  1. veronika_elizarova
    02.09.2024 04:56
    +1

    Достаточно понятный и подробный мануал без лишней воды, оказался актуален для моей задачи. Благодарю автора