Отбираем хлеб у нативных разработчиков: миграция с Kotlin/Swift на RN
December 12, 2024
preview

Меня зовут Александр Чернов, я фронтенд-разработчик в KODE и я использую React Native в разработке мобильных приложений уже более семи лет. Сейчас расскажу вам, как мы у нативных разработчиков хлеб отбирали.

Однажды к нам пришел заказчик с MVP от другой команды. Это были iOS и Android-приложение. iOS-приложение было написано на Swift, сверстано в сториборде, с архитектурой MVC (Massive Model View Controller). Android-приложение было написано на Kotlin, сверстано в XML. Архитектура отсутствовала — только два слоя, data и UI. Причем Activity или Fragment из UI-слоя содержали в себе всю бизнес-логику, которая зачастую дублировалась.

При ревью кодовой базы мы поняли, что в дальнейшем поддерживать MVP и добавлять новую функциональность будет больно. Поэтому решили основательно доработать продукт, чтобы при масштабировании бизнеса и внедрении новых фичей заказчику не пришлось переписывать код.

Для реализации мы рассматривали такие варианты:

  • Поддерживать нативное приложение, отрефакторить его и в дальнейшем дорабатывать на Kotlin/Swift. Но в таком случае бюджет проекта увеличился бы, поскольку заказчику нужно было бы задействовать iOS и Android-разработчиков.

  • Остановить поддержку Kotlin/Swift и разрабатывать приложение на React Native с нуля. Для заказчика этот вариант не подходил, так как для стартапа было важно быстрее получить готовый функционирующий продукт и начать разработку новых фич.

  • Поддерживать нативное приложение на Kotlin/Swift и постепенно переписать на React Native.

Мы обсудили перспективы с заказчиком и остановились на последнем варианте — плавном переезде на RN. У него было сильное преимущество: ранее на другом проекте мы уже опробовали React Native с этим заказчиком, и он остался доволен результатом. К тому же, у нас была уже собрана команда и наработана база, поэтому заказчику не пришлось заново проходить эти процессы и увеличивать стоимость проекта.

Как мы «переезжали»

1. Потушили пожары

Сначала мы с минимальными усилиями сделали ребрендинг приложения, скрыли лишний функционал и исправили множество багов. Например, столкнулись с проблемой несогласованных моделей данных между клиентом и сервером. Также в приложении на Android отсутствовал некоторый функционал, который был на iOS — например, Google reCAPTCHA.

В общем, мы провели минимальный рефакторинг и выпустили приложение в TestFlight, чтобы бизнес мог с ним работать и параллельно проходить проверку в сторах.

2. Начали внедрять React Native

У React Native есть все необходимые инструменты и подробная документация, чтобы внедрять его в уже существующие нативные приложения.

Минимальная сущность, в которую рендерится RN-приложение для iOS — View, а для Android — Fragment. Зная это, я написал набор утилит, которые позволяют буквально в пару строчек запустить React-приложение. Получилось довольно лаконично, достаточно передать имя начального роута.

Android:

iOS:

Если возникает необходимость передать дополнительные данные, с этим также не возникает проблем — в JS-слое регистрируем компонент App,

который принимает переданные из нативного слоя свойства.

Для управления нативной навигацией по экранам, а также передачи данных между RN-представлением и нативным слоем был написан Native Module, который мы назвали AppBridge (см. схему ниже).

Визуально это выглядит так. Нативный слой доминирует и занимает всю площадь экрана, нижний таббар тоже нативный. Но контент внутри таба — это React-приложение со своими моделями данных, навигацией, сетевым слоем и так далее.

Напомню, что в Android основной экран с таб-навигацией — Activity, а каждый таб — Fragment. Мне достаточно было добавить новый или заменить существующий на RN фрагмент, чтобы при переходе на таб запускалось RN-приложение с нужным флоу. Очень просто. В iOS тоже самое: UITabBarController для каждого таба рендерит UIViewController, мы также легко можем добавлять новые или изменять текущие контроллеры на RNViewController (см. код выше).

Как это работает под капотом: нативный слой запускает React Native-представление, которое запускает React-приложение с начальными параметрами, такими как токен авторизации, ссылка на API, имя роута и другие.

Как видно из схемы, React-приложение может асинхронно обмениваться данными с нативным слоем и непосредственно с RN-представлением в обе стороны через AppBridge (Native Module).

Также можно заметить, что все RN-представления открывают одно и то же React-приложение, просто с разными входящими параметрами. Слой React Native намеренно сделан моноприложением, так как конечной целью было оставить одну точку входа. Но ничто не мешает организовать и микроприложения, чтобы у каждого из них была своя песочница и не было общего контекста. А RN Utils — это набор самописных утилит для работы с RN, туда входят классы RNFragment, RNViewController и другие.

3. Мигрировали на React Native

Следующим шагом была замена флоу авторизации/регистрации. Теперь React Native получает авторизационный токен, сохраняет его, рефрешит и передает в нативный слой. Также управляет биометрией, логаутом и т.д.

А еще мы добавили CodePush — киллер-фичу React Native, которая позволяет обновлять мобильное приложение без прохождения проверок в сторах.

Постепенно заменили все табы и отдельные Activity/UIViewController на RN-представления. Но основная навигация по приложению — неавторизованная зона, флоу авторизации, основной экран с табами и другие экраны, до сих пор осуществляется в нативном слое.

Наконец, остался последний нативный раздел. Прежде чем переделывать его, мы доработали приложение так, чтобы React Native начал доминировать. Получилось полноценное RN-приложение, запущенное в одном Activity/UIViewController, в котором вся навигация осуществляется через react-navigation. Оставшийся раздел я завернул в Native UI Component (документация Android, документация iOS) и использую его в React-приложении.

Финальным шагом мы переделали последний нативный раздел на RN и выпилили оставшийся мусор. Теперь это полностью React Native приложение, как будто мы написали его с нуля.

Преимущества и недостатки подхода

React Native — крутая и зрелая технология, которая хорошо себя проявляет. Забегая вперед, скажу, что при ее внедрении в существующее приложение я не встретил непреодолимых проблем, а процесс был подробно описан в документации.

Рассмотрим преимущества и недостатки нашего подхода.

Преимущества

  • Нон-стоп. Мы бесшовно переписали приложение, улучшили пользовательский опыт, кодовую базу и поддерживаемость продукта, и при всем этом не тормозили бизнес.
  • Снижение стоимости разработки. При переходе на кроссплатформу над проектом работает одна команда вместо двух, то есть вместо iOS и Android — React Native.
  • Увеличение скорости разработки. Инструменты для React Native очень развиты и позволяют быстро разрабатывать приложения. В Storybook можно сверстать приложение практически полностью и посмотреть, как оно будет выглядеть. Плюс hot-reload — мы пишем код, он сразу же обновляется и мы видим результат. Тот же самый CodePush — очень крутая вещь.

Недостатки

  • Проблемы, с которыми мы бы не столкнулись при обычной разработке. Например, работа с диплинками или кнопкой «Назад» на Android. Когда на экране два слоя — нативное приложение и React Native, они оба начинают обрабатывать эти действия, происходят некоторые конфликты и приходится тратить время на их решение.Высокий порог входа для React Native-разработчика. Помимо React Native, ему нужно неплохо разбираться в нативных платформах, писать нативный код и в целом понимать, как все работает.
  • Высокий порог входа для React Native-разработчика. Помимо React Native, ему нужно неплохо разбираться в нативных платформах, писать нативный код и в целом понимать, как все работает.

P.S. Возможно, вы хотите спросить, где исходники RNViewController, RNFragment и RN Utils. Это были временные, узкоспециализированные решения, которые помогли нам переехать на RN, а после переезда потеряли ценность и были удалены из проекта. Поэтому исходники мы показать не можем.

Спасибо за внимание!

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies