Когда имеет смысл писать кроссплатформенные приложения: появление и исчезновение React Native в Lingualeo

    В приложениях Lingualeo сложился довольно редкий кейс. Их создали до того, как появились кроссплатформенные технологии, но через несколько лет туда добавили модули на React Native. Кроссплатформенные модули прожили в приложениях примерно четыре года: в ближайшем релизе мы их уберём.

    Мы попросили лидера мобильной разработки Артёма Рыжкина (phoenix_rav) рассказать о том, откуда в нативных приложениях Lingualeo появились модули на React Native, какие они вызывали проблемы и когда вообще имеет смысл делать кроссплатформенные приложения. 

    Как в приложении Lingualeo появились кроссплатформенные элементы

    «Приложение Lingualeo появилось в русских сторах в 2012 году. Тогда ещё не было кроссплатформенных технологий, приложение для iOS написали на Objective-C, а для Android — на Java. Их поддерживали и развивали две отдельные команды. 

    Со временем приоритеты в компании несколько раз менялись. После 2015 в Lingualeo сделали упор на веб: в мобильной команде осталось всего несколько разработчиков. В основном они поддерживали и актуализировали версии библиотек, и редко добавляли новое.

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

    В 2016 году в Lingualeo родилась идея игровых тренировок — комплексов заданий, которые помогают пользователям учить язык. Часть была платная, часть — бесплатная. Всего их придумали шесть.

    Три тренировки по чтению. На скриншотах тренировка, в которой прочитанный текст нужно заново собрать из фрагментов. В других тренировках пользователю нужно разделить текст пробелами на слова или вставить пропущенные слова.
    Три тренировки по чтению. На скриншотах тренировка, в которой прочитанный текст нужно заново собрать из фрагментов. В других тренировках пользователю нужно разделить текст пробелами на слова или вставить пропущенные слова.
    Две аудиотренировки, где пользователь слушает текст или предложение и затем собирает его из фрагментов. Например, восстанавливает прослушанный текст из из слов с вариантами ответа.
    Две аудиотренировки, где пользователь слушает текст или предложение и затем собирает его из фрагментов. Например, восстанавливает прослушанный текст из из слов с вариантами ответа.
    Одна грамматическая тренировка. В ней пользователь видит предложение на родном языке и затем по словам собирает его на изучаемом языке. Для каждого слова есть варианты ответа.
    Одна грамматическая тренировка. В ней пользователь видит предложение на родном языке и затем по словам собирает его на изучаемом языке. Для каждого слова есть варианты ответа.

    В команде приняли решение делать игровые тренировки в приложениях на кроссплатформенном языке. Этим хотели добиться параллельности релизов, унифицированности команды, повышения скорости разработки.

    Для работы выбрали React Native. Помимо него рассматривали несколько других кроссплатформенных технологий, например, подключаемые библиотеки, игровые движки и Webview. Все варианты оценили по скорости, удобству и качеству разработки, и в итоге выбрали React Native.

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

    В итоге когда я пришёл в команду в 2018 году, то увидел интересную ситуацию. Как минимум я с таким раньше не сталкивался: в компании нативные приложения на iOS и Android, но в них есть шесть тренировок, написанных на фреймворке React Native. Получается, приложения по большей части нативные, но в чём-то — кроссплатформенные.

    Почему кроссплатформенные элементы в нативных приложениях — это проблема?

    Кроссплатформенный подход стал популярным в том числе потому, что предполагается, что с ним не нужно дублировать одинаковую логику на двух мобильных платформах. Вместо этого вы пишете единый код на одном из кроссплатформенных фреймворков. Но в нашем случае это было не совсем так: кроссплатформенные элементы только добавили сложностей и замедляли разработку. Вот с какими сложностями можно столкнуться:

    React Native не позволяет полностью отказаться от нативного кода, заменив его кроссплатформенным. Для некоторых функций, например, проигрывания музыки, обращения к сенсорам устройства или к локальному хранилищу придётся писать так называемые bridge-классы.

    Другой вариант — подключать сторонние node модули, реализующие необходимую функциональность. Но может сложиться ситуация, что библиотеку всё равно придётся доработать под нужды вашего приложения. К тому же команда, разработавшая модуль, со временем может перестать его поддерживать.

    У нас использовались bridge-классы — код на двух нативных языках: Java/Kotlin для Android и Objective-C/Swift для iOS. Мы написали их для обращения к медиаплееру, к аналитическим системам, локальному хранилищу и логике авторизации. 

    Например, вот как bridge-класс запускает Intent Service в Android:

    public class LLMergeBridgeModule extends ReactContextBaseJavaModule {
    
       protected static final String MODULENAME = "LLMergeBridgeModule";
    
       @Override
       public String getName() {
           return LLMergeBridgeModule.MODULENAME;
       }
    
       public LLMergeBridgeModule(ReactApplicationContext reactContext) {
           super(reactContext);
       }
    
       @ReactMethod
       public void merge() {
           ReactApplicationContext context = getReactApplicationContext();
           SyncService.startServiceForceSync(context);
       }
    
    }

    У разработчика также могут возникнуть проблемы, если на кроссплатформенном фреймворке необходимо реализовать кастомную логику обработки жизненного цикла экрана Activity в Android или ViewController в iOS.

    В нативных платформах со временем меняется логика работы: если в команде только разработчик на React Native, ему придётся следить ещё за двумя платформами. Когда выходят новые версии Android или iOS, нужно переписывать bridge-классы, тратить дополнительное время на поддержку. Разработчику нужно следить не только за развитием кроссплатформенного фреймворка, на котором он работает, но и за обновлениями обеих мобильных платформ.

    Сложно искать разработчиков: нужен дорогой универсальный специалист или несколько разработчиков разного профиля. Если вы хотите создать приложение на React Native, можно найти разработчика, который знает одновременно iOS, Android и React Native. 

    Второй вариант — искать 2–3 разработчиков, каждый из которых разбирается хотя бы в одной платформе. Но тогда усложняется отладка приложения. Например, разработчик, который знает React Native и Android, будет постоянно обращаться за помощью к коллеге, который знает iOS. Это замедляет процесс. 

    Если в приложении есть и нативные компоненты, и части на React Native, то может возникнуть дублирование логики. Нам пришлось дублировать общие элементы интерфейса и заново писать для React Native сетевой слой, который уже был в нативном приложении.

    Например, на момент внедрения React Native у нас уже было два проекта для локализации строк в системе переводов. Один работал для приложения на Android, другой — для iOS. Каждый из проектов учитывал особенности своей платформы.

    При подключении React Native нужно было завести третий проект, но тогда бы сложилась ситуация дублирования строк: одна и та же строка была бы заведена в 2 проекта сразу. Это бы потребовало дополнительного труда переводчиков. Поэтому мы решили использовать маппер для преобразования строк из iOS-проекта в формат для React Native. 

    С августа 2019 все приложения Google Play должны обязательно поддерживать 64-битную архитектуру. Если для нативного приложения такая поддержка включалась парой строчек кода, то для React Native пришлось мигрировать на последнюю версию. Нативный разработчик несколько недель разбирался в коде, чтобы адаптировать его под новую версию.

    Из-за всех этих трудностей получилось так, что поддержка 6 тренировок — небольшой функциональности относительно наших Джунглей или Словарей — требовала несоразмерно много ресурсов. Мы решили избавиться от кроссплатформенной части и перейти на полностью нативные приложения. 

    Как мы вернулись к полностью нативным приложениям

    Сначала мы наняли для этой задачи аутсорсера. Мы предположили, что сторонняя команда справится быстрее, чем наши разработчики, потому что у нас было много текущих задач. Команда разработки продолжала заниматься приоритетными проектами, а аутсорсеры начали переписывать игровые тренировки с React Native на нативные языки. Первым взялись за версию для iOS. 

    Работа шла дольше, чем мы предполагали. Аутсорс-команда отдавала нам сборку, которую нужно было проверить, поэтому у нас выросла нагрузка на QA. Мы проверяли сборку, давали комментарии, их брали в работу, но процесс шёл медленно. Миграция одной тренировки в среднем занимала 3 месяца от получения ТЗ до отправки в прод. 

    Для ускорения процесса мы решили взяться за миграцию тренировок самостоятельно. Версии для iOS закончили аутсорсеры, а Android мы взяли сами. Начали с одной тренировки в экспериментальном режиме: получилось быстрее и дешевле, чем на аутсорсе.

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

    Дополнительный плюс от перехода на полностью нативные приложения оказался в том, что размер архива приложения уменьшился на 28 Мб. Также сократилось время сборки: раньше билд релиза Android-приложения на Mac mini занимал до 20 минут на холодном старте, а сейчас — 2.

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

    В каких случаях выгодно делать кроссплатформенное приложение?

    React Native вполне подойдёт для многих проектов. Но, по нашему мнению, его выгоднее всего использовать, если ваша задача одновременно соответствует шести критериям: 

    1. Это простое приложение. Такое приложение не требует реализации сложной бизнес-логики и сложной UI-логики. Например, витрина интернет-магазина: приложение должно просто отобразить список товаров, дать возможность положить товар в корзину и оплатить покупку.

    2. Это приложение должно быть одинаковым на каждой платформе. Если ваше приложение выглядит и построено так, что не будет отличаться в зависимости от платформы, используйте React Native. В таком случае получится сэкономить деньги бизнеса и время разработчиков за счёт унификации кода.

    3. Это приложение не работает с медиафайлами, сенсорами и навигацией. React Native — удобная технология для мобильных приложений, которым не требуется работать с медиафайлами и множеством сенсоров смартфона. 

    4. Вам важно быстро запуститься и вносить изменения на лету. Кроссплатформенные технологии — отличный вариант для быстрого запуска. React Native будет выгодно использовать, например, стартапам. Скорее всего, кроссплатформенная разработка потребует меньше человеко-часов и предприниматель быстрее получит продукт. Его можно тестировать, показывать инвесторам и при необходимости менять код на лету без пересборки проекта, используя механизм code push.

    5. У вас пока нет нативных приложений. Добавление кроссплатформенности в нативные приложения приведёт к дублированию сетевого слоя, логики работы UI, локализации, также появятся проблемы с настройками проекта.

    Когда кроссплатформенные решения не выигрывают у нативных?

    Есть кейсы, когда React Native точно не будет выгоднее, чем нативное приложение. Вот несколько таких:

    В приложении нужны сложные механики с медиа-ресурсами. React Native не справится с проигрыванием медиа без bridge-классов. Если в приложении будут многоступенчатые механики, связанные, например, с воспроизведением музыки, то реализовывать это на React Native может быть сложнее, чем в нативных приложениях.

    В Lingualeo такие механики как раз есть в игровых тренировках. Например, в аудиотренировке текст сначала проигрывается до конца, затем разбивается на фрагменты. Пользователь может воспроизводить их и перетаскивать при помощи drag and drop.

    Приложение будет много обращаться к сенсорам. Чтобы обратиться, например, к GPS, Bluetooth, акселерометрам, датчику лица или микрофону, коду на React Native также нужны bridge-классы. 

    В приложении будут встроенные покупки. Внутренние покупки — очень важная и чувствительная для пользователей фича в приложении. При её реализации лучше обращаться к нативным библиотекам Google и Apple, чтобы, например, чётко понимать, какие проблемы возникли на этапе покупки и верно обрабатывать состояния. Придётся внедрять в приложение нативный код, который будет обращаться к сторам, и для каждой платформы эти модули будут разными. 

    Фичеринг приложения и следование гайдлайнам. Если вы хотите активно получать фичеринг от Google Play и Apple Store, то, скорее всего, от команды разработчиков потребуется внедрение новой функциональности, специфичной для каждой из двух платформ. Поддержка подобных фичей в React Native потребует времени со стороны разработчиков, а сторонний node module может появиться с заметным опозданием.

    Выводы

    React Native — мощная, удобная и во многом универсальная технология, но лучше всего она подойдёт для приложений, которые:

    • не имеют нативной кодовой базы: то есть, сразу создаются как кроссплатформенные;

    • основаны на простой логике: им не нужен доступ к медиафайлам и датчикам;

    • не имеют встроенных покупок, например, платного премиум-доступа или игровой валюты;

    • должны запуститься быстро и экономно: например, чтобы показать MVP инвесторам».

    Lingualeo
    Компания

    Комментарии 13

      0
      В каких случаях выгодно делать кроссплатформенное приложение?

      У вас пункт 3 пропущен.
        0
        Спасибо, поправили.
        +2
        Не совсем понятно зачем использовать ReactNative(RN), если уже есть приложения написанные на native стеке и вы от них не собираетесь отказываться. Использование большого «зоопарка» в технологиях всегда сопряжено с усложнением кода и его поддержкой.

        Про использование специфичных возможностей каждой платформы, я бы с вами согласился, если бы кейсы, которые вы описываете, были бы таковыми. Но для работы с оплатами, GPS и файлами есть уже написанные библиотеки (если не хотите заново писать код), которые работают.

        Про переход на 64Bit версию Android тоже не совсем верно. Поддержка появилась в версии 0.59 и переход на неё не был сколько-нибудь сложным, в отличии от перехода на версии 0.60+.

        В общем я соглашусь с вами в том, что использовать RN, когда уже есть native, не совсем целесообразно. Но не могу согласиться с выводом, что на RN можно писать только простые приложения аля витрина магазина. Можно посмотреть к примеру сюда.
          +1
          Слежу краем глаза за флаттер чатиком и с выводами статьи в целом согласен. Пока дело касается разработки UI и применения готовых плагинов — вроде проблем особых у людей нет. Как только нужно подправить библиотеку или написать свою, интегрировать с нативным кодом, разобраться с ошибкой в нативной части и подобное — тут у тех кто понадеялся на наличие готовых решений начинаются проблемы.
            +2
            Спасибо за комментарий!

            Абсолютно согласен, что при наличии полностью нативного приложения внедрение кроссплатформенной составляющей добавляет проблем в поддержке.

            Да, библиотеки, возможно, есть, но тогда встает вопрос поддержки и кастомизации того, что написано другим разработчиком. А самое главное — скорость обновления библиотек. Если, например, в Google API изменится логика работы с оплатами, а разработчик, который ее поддерживал, не внес изменения, а вы об этом не знаете, так как надеетесь на библиотеку, то можете получить дополнительные проблемы.

            У нас была сравнительно старая версия React Native в проекте 0.43, и переход с нее на 0.59 оказался довольно болезненым.

            С примерами можно согласиться, но Airbnb в итоге отказался от использования кроссплатформенности, Facebook-приложения, в том числе Instagram, используют свой стэк по сути (React Native все-таки разработка Facebook, было бы странно, если бы они использовали что-то другое). Walmart, по сути, является интернет-магазином.

            С остальным спорить не буду: каждый выбирает то, что ему удобнее. Остается вопрос, сколько проблем они готовы решать для того, чтобы использование React Native в их приложениях оправдался. Не могу судить о том, сколько у них уходит усилий на то, чтобы обработать ту или иную особенность платформ. Для меня кейс Airbnb является наиболее показательным.
            +1
            Для начала, стоит вообще серьёзно засесть за пиво стол и честно глядя друг другу в глаза спросить: а нафига нам эти платформы?! КТО и где будет приносить нам деньги? И насколько приложение вообще будет привлекательно, чтобы за него кто-то вообще платил.
            И сразу обнаружится, что 99% приложений вообще не нуждаются ни в каких «кросс-платформенностях». У смузихлёбов это просто бзик, хайп в заднем проходе — «кроссплатформенность». И только потом, профукав время и деньги заказчиков/себя, начинается прозрение.

            Забавно, но кроссплатформенность — она не нужна юзерам — она нужна разработчикам! Очень ленивым/жадным разработчикам, которые в 21 веке думают, что можно «экономить на коде». Реальность показывает, что скупой не просто «платит дважды», а вообще вылетает из бизнеса.

            «Нэйтив» — это чудовищный «подводный слой» функциональности и поведения, над которым работали годами профессионалы. Таскбар, трэй, меню, уведомления, жесты, тени, шрифты, отточенные контролы… да надо быть глупцом, чтобы думать, что всё это можно прикрыть фúговым листиком жабоскрипта и сверху нафигачить «кроссплатформенных кнопок»! Чудики, ей богу!
              +3
              Справедливости ради, хоть RN я терпеть не могу — он использует нативные UI элементы через мост, а не сам рисует. Тем что вы описали страдает флаттер, дарт в котором компилируется aot в машинный код рисует через skia на канвасе напрямую, используя opengl/metal, как игровые движки.
              Я нативщик, но в целом флаттер мне видится лучшей альтернативой чем засилье RN, электронов и подобного.
                +1
                Этого мало, я же описал ЧТО ИМЕННО в системе (помимо внешнего вида) помогает юзерам:

                Таскбар, трэй, меню, уведомления, жесты, тени, шрифты, отточенные контролы


                Любой, кто сидит хотя бы год в своих иОСиках, Вендах и Линупсах, мгновенно просекает, когда «что-то не то». Когда приложение — лишь нашлёпка над очередной «кроссплатформенной муетой», а не родная программа.

                По мне, при том обилии девелоперов и уже написанного софта, просто глупо писать очередной «кроссплатформенный редактор» — он будет «наибольшим общим делителем» всех платформ и вот нафиг не нужен! Лучше нативного ты всё равно не напишешь.
                  0
                  Таскбар, трэй, меню, уведомления, жесты, тени, шрифты, отточенные контролы

                  В том и дело что RN тут исползует эти же компоненты, нативные. Другое дело что у него есть другие проблемы. Во первых они их любят настраивать так что перестают походить на натив. Во вторых RN лагает. В третьих — гайдлайнам следовать тоже не любят.

                  Опять же, если один черт бизнес будет экономить и разрабатывать кроссплатформу — я бы предпочел видеть у себя на мобилке или на десктопе флаттер, чем электрон, кордову, реакт нейтив и другие веб штуки. А бизнес будет экономить, даже зная о минусах кроссплатформы.
                0

                Все так, но история ничему не учит. Swing против SWT, QT или wxWidgets. Желание сэкономить будет порождать обертки вроде RN и SWT и рендеры вроде Flutter и QT с одним и тем же результатом — все это оседает в нишах, тортовые продукты делаются на нативном UI.

                +8
                Почему мы отказались от React Native? Потому что в нем нет хранимых процедур)
                  +1

                  Вы не ответили на этот комментарий под предыдущей статьёй, ну да может он просто потерялся. Может ответите тут. Что думаете насчёт этого?
                  https://twitter.com/SanSYS/status/1299657221085835264


                  Как я понял, после перехода на хранимки ваша база позволяла писать в неё любые объекты и выгружать всё содержимое, включая хэши паролей и, возможно, персональные данные. Интересно ваше мнение по этому поводу.

                    –1
                    Добрый день! Мы проверили ситуацию, описанную пользователем в этом треде. Указанные им факты не подтвердились.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое