Вступление
Всем привет. Какое-то время назад, я решил делать свой проект для Android и iOS одновременно. Естественно, встал вопрос о выборе технологий. Пару недель присматривался к популярным стекам и выбрал Kotlin/Native. Поскольку я являюсь Android-разработчиком, то с Kotlin знаком давно, а со Swift особого опыта не было и хотелось получить большую часть кода общего для обеих платформ. Следовательно, сразу встал вопрос, а как писать UI для iOS. Быстрый взгляд на рынок подсказал, что есть Flutter, который позволяет писать UI для двух платформ одновременно. Собственно, так и началась эта история.
В статье описан опыт сборки Flutter в качестве UI и Kotlin для основной логики. Важно: под катом много картинок и инструкция, как собрать проект
Оглавление
- Создание общей библиотеки на Kotlin
- Создание Android-приложения
- Создаём iOS-проект
- Добавление Flutter в Android-приложение
- Добавление Flutter в iOS-проект
- Заключение
Часть 1
Создание общей библиотеки на Kotlin
- Выбираем Kotlin Mobile Shared library
- Далее
- Указываем нашу рабочую папку, здесь я сделал отдельную папку под проект. Поскольку у меня будет 4 разных проекта и их удобнее держать в одном месте
- Осталось указать в
local.properties
путь кsdk.dir
и проект начинает собираться, у меня это путь/Users/vlad/Library/Android/sdk
- Структура проекта, изменяем имена пакетов с
sample на
habr.example
- Пора приступать к публикации, вызываем
wrapper
. После этого у нас в проекте появится файлик.gradlew
и можно будет с ним работать из терминала
- Запускаем из терминала
./gradlew publishToMavenLocal
- После этого в локальном maven-репозитории у нас появится 4 папки, в которых будут лежать наши библиотеки
Часть 2
Создание Android-приложения
Создаём android проект
Тут всё стандартно
- На момент написания статьи проект генерируется с «битой» зависимостью, поэтому убираем в конце
jre7
, получитсяkoltin-stdlib
, после этого проект начинает собираться
- Открываем
build.gradle
и в секциюrepositories
добавляемmavenLocal
. Важно! Секцияrepositories
должна быть та, что внутриallprojects
, а не вbuildScript
- Теперь мы можем добавить нашу библиотеку как зависимость
implementation 'habr.example:commonLibrary-jvm:0.0.1'
- Открываем
activity_main.xml
и указываем вTextView
idmain_activity_text
- В
MainActivity.kt
просто устанавливаем текст в этомTextView
- Отлично, к этому моменту у нас есть Android приложение, которое умеет использовать функцию
hello()
из нашей библиотекиhello() на эмуляторе
Часть 3
Создаём iOS-проект
- Выбираем Single View App
- Заполняем основную информацию и выбираем папку. Здесь важно выбирать папку, корневую для наших остальных проектов, поскольку в ней Xcode создаст подпапку с название проекта
- Первым делом добавляем
CocoaPods
. Для этого выполняемpod init
в папке проекта, закрываем проект вXcode
и выполняемpod install
. Видим, что установка завершилась успешно
- Важно! На сайте
CocoaPods
не рекомендуется добавлять папку/Pods
в.gitignore
, но я всё равно это сделал. Так как после добавленияflutter
, мы каждыйbuild
будем реконфигурировать зависимости. Пока что, мне это решение нравится больше, чем засорять.git
- Открываем проект через файлик
Awesome App.xcworkspace
- Открываем терминал, в нём переходим в папку нашей
commonLibrary
и запускаем./gradlew linkDebugFrameworkIos
. После этого в нашей папкеbuild
появляетсяiOSFramework
- Выбираем Target
- И для этой Target добавляем бинарник
- Выбираем Add Other
- Указываем путь к
framework
, который получили на шаге 6 (commonLibrary.framework
)
- Теперь в проекте у нас должен отображаться этот
framework
- Идём в
Build Settings
и отключаемEnable Bitcode
- Теперь нужно указать где именно искать наш
framework
, открываемFramework Search Path
- Указываем путь
"${PODS_ROOT}/../../commonLibrary"
. Обязательно выбираемrecursive
. Конечно, можно обойтись и без этого, если более точно сконфигурировать путь. Но, поскольку это только начало проекта, сейчас нам важно убедиться в том, что вся эта связка заработает. А поменять пути мы можем и потом
- Нужно сделать так, чтобы при каждом
build
вXcode
собирался нашframework
при помощиgradle
. ОткрываемBuild Phases
- Добавляем новую
Script Phase
- Добавляем код скрипта.
cd "${PODS_ROOT}/../../commonLibrary" echo $(pwd) ./gradlew linkIos
Здесь мы просто переходим в папку проекта нашей библиотеки и запускаем./gradlew linkIos
. Вызовecho $(pwd)
нужен только для того, чтобы показать в консоли, в какую конкретно папку мы попали
- Сдвигаем нашу
build phase
в самый верх, сразу послеtarget dependencies
- Теперь открываем
ViewController
и добавляем вызов нашей функции из библиотеки
- Запускаем наш проект и видим
Отлично, это значит, что мы правильно подключили Kotlin Library к iOS-проекту
Осталось всего ничего — добавить flutter, как framework для написания UI, в наши приложения и можно начинать разрабатывать продукт
Часть 4
Добавление Flutter в Android-приложение
Тут мне очень помогла статья на github
- Переходим в root-папку, где лежат все наши проекты и делаем
flutter create -t module flutter_ui
- Открываем
settings.gradle
и включаем наш flutter-модуль, как подпроект
- Открываем файл build.gradle и добавляем в зависимости наш проект
- Изменяем
MainActivity.kt
наFlutterActivity
- Добавляем
App.kt
, в котором будем инициализироватьFlutter
при старте приложения
- Изменяем манифест и говорим, что теперь у нас есть класс для
Application
- Обязательно добавляем
java8
, без этого flutter не запустится
- Видим UI и в логах Hello from JVM, а это значит, что мы собрали вместе UI на Flutter и основную библиотеку на Kotlin/Native
- Добавим в
MainActivity.kt
метод, который мы будем вызывать из Flutter. Здесь по событию из Flutter мы возвращаем нашhello()
из kotlin-library
- И добавляем в
main.dart
код, который будет вызывать метод вiOS/Android-части
приложения
- Получаем
Часть 5
Добавление Flutter в iOS-приложение
- Обновляем наш
Podfile
flutter_application_path = File.expand_path("../flutter_ui", File.dirname(path)) eval(File.read( File.join( flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
- Важно. Добавьте
$(inherited)
в первую строчкуframework search paths
. Обязательно проверьте что у васframework search paths
не пустые
Когда вы меняете зависимости вsome/path/my_flutter/pubspec.yaml
, вам нужно запуститьflutter packages get
изsome/path/my_flutter
, чтобы обновить зависимости вpodhelper.rb
. После этого нужно запуститьpod install
изsome/path/MyApp
- Добавим ещё 1
Build Phase
, только уже для Flutter. Выше того, что мы добавляли в части 3Script phase
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
- Заменим наш
AppDelegate
наFlutterAppDelegate
- Обновим
ViewController
- Обернем наш
ViewController
вNavigatorController
- Теперь приложение запускается. Но, пока что, у нас нет связи между библиотекой и flutter
- Добавим эту связь с помощью
FlutterMethodChannel
- Отлично, теперь и iOS приложение использует
flutter
дляUI
иkotlin
для основной логики
Заключение
Что важно здесь сказать: я не претендую на то, что вы узнали что-то новое или уникальное. Я просто поделился свои опытом, поскольку для того, чтобы заставить это всё работать вместе, потратил около 4х рабочих дней. И не смог найти примеров кода проекта, который использует одновременно и Kotlin/Native и Flutter
Итоговые проекты
Список ссылок, которые мне помогли, но не сразу
- Сам flutter
- Связь между нативным кодом и UI flutter platform-channels
- Добавить flutter в существующее приложение github
- Kotlin Native native-overview
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Стоит ли развивать тему и писать про архитектуру этого проекта?
97.33% да, мне интересно будет прочитать146
2% нет, мне и так хватает материала по теме3
0.67% я сам напишу статью про flutter + kotlin1
Проголосовали 150 пользователей. Воздержался 31 пользователь.