В своей предыдущей статье я рассказал о том, почему считаю, что мы можем значительно улучшить управление UI State (состояние пользовательского интерфейса) между View (представление) и ViewModel (модель представления) в Android, используя архитектуру Model-View-Intent (модель-представление-намерение) (MVI) с помощью Finite State Machine (машина с конечным числом состояний. конечный автомат) (FSM). В этой статье я подскажу вам шаги, необходимые для модернизации этого решения до уровня Kotlin Multiplatform Mobile (KMM), где можно воспользоваться общим исходным кодом, содержащим MVI+FSM, так что обе платформы — Android и iOS — могут унаследовать его преимущества, отвечая только за платформозависимые реализации: UI/UX. Перед тем как мы начнем, я предположу, что читатель имеет базовые знания о KMM, о том, как настроить проект, как создать общий код, как запросить имплементации, соответствующие платформе (expect/actual (ожидаемые/фактические)), и прочитал мою предыдущую статью. Android: Jetpack Compose и Flow (Job). iOS: SwiftUI и Combine (Publishers и Subscription). После создания нового проекта KMM нам необходимо убедиться, что мы можем использовать наши имплементации FSM и MVI. FSM: State Machine от Tinder еще не прошла апгрейд для использования в качестве мультиплатформенной библиотеки, но, к счастью, существует пулл-реквест (PR) с такой имплементацией, которая на самом деле довольно проста. Пока этот PR не будет принят и опубликован, один из вариантов — скопировать StateMachine.kt и добавить его в наш проект в shared (общий) модуль. Примечание: Если вам интересно, почему мы не можем воспользоваться сервисом JitPack, то по этому поводу имеется соответствующий материал. MVI: Библиотека Orbit Multiplatform — можно догадаться по названию — уже является мультиплатформенной. Orbit также предоставляет нам swift-gradle-plugin для генерации хелпер-классов .swift, так что нам не нужно беспокоиться о том, как все работает под капотом. Для прослушивания изменений состояния мы просто используем ObservableObject внутри View, а коммуникации Combine/Flow и жизненные циклы автоматически управляются за нас. Примечание: на момент написания статьи авторы находятся в процессе обновления для новых версий Kotlin. Сейчас он не работает с версиями, начиная с 1.6.0. Этот плагин делает за нас всю работу по генерации кода, но я считаю, что нам полезно знать, что происходит за кулисами, поэтому предлагаю вам пройтись по логике создания этих классов. В конце концов, мы не так уж сильно зависим от него. ViewModel: Чтобы воспользоваться преимуществами общей области жизненного цикла - для запуска корутины, которая будет прервана при очистке ViewModel, я буду использовать ViewModel библиотеки IceRock moko-mvvm (dev.icerock.moko:mvvm-core:$ {latest-version}) в качестве родительского класса нашей общей ViewModel (вместо Android's). Для иллюстрации этого путешествия я буду использовать тот же проект, что и в предыдущей статье. Как вы можете увидеть ниже, результат будет одинаковым, и это потому, что мы используем преимущества одной и той же бизнес-логики и архитектурной реализации — написанной, протестированной и валидированной один раз — оставляя только имплементацию пользовательского интерфейса для каждой платформы. В этом и заключается вся прелесть KMM. Пользовательский интерфейс Android уже готов, нам не нужно его менять. Следующие шаги таковы: Совместное использование архитектуры FSM+MVI и ViewModel; Обработка жизненных циклов Flow и Publisher; Использование изменений состояния на iOS. Весь код FSM и MVI будет перемещен в папку commonMain внутри модуля shared: Koin поможет нам справиться с этой задачей. Сначала нам нужно создать класс, в котором мы определим expect (ожидаемые) "правила" : commonMain Этот класс находится в папке commonMain и содержит логику инициирования внедрения зависимостей. Далее нам нужно создать по одному классу для каждой платформы с их actual (фактической) имплементацией: androidMain iosMain Они очень похожи, но в случае имплементации на iOS нам нужно раскрыть геттер для ViewModel. На Android Koin предлагает удобные расширения getViewModel. Архитектура в общем доступе. Для использования эмиссий состояния из нашего общего кода мы используем Kotlin Flows, но нам нужно навести мосты между ними и Swift Combine Publishers. Следующий код был составлен на основе очень познавательной статьи Джона О'Рейли. Он поможет нам этого достичь и обработать жизненный цикл Publisher на iOS. Начнем с создания функции расширения, которая возвращает фоновый Job, заданный Flow: iosMain Далее внутри iosApp нам нужно создать Subscription, которая будет содержать экземпляры Flow и Job, чтобы управлять для нас логикой subscribe и cancel : Мост между Flow и Publisher Все элементы, полученные Flow, будут переданы subscriber. Кроме того, при вызове функции Flow onComplete, subscriber также будет завершен. Следовательно, будет вызвана функция cancel(), которая очистит subscriber и отменит job. Если вы помните, наша архитектура MVI привязана к viewModelScope, что означает, что когда ViewModel очищается, то очищается и Flow, и Publisher. Жизненный цикл обработан. Прежде чем перейти к следующему шагу, давайте добавим это удобное расширение: Последним шагом этой миграции является раскрытие UI State как Published переменной. Для этого мы создадим класс-обертку, соответствующий протоколу ObservableObject. Этот класс будет содержать экземпляр из общей ViewModel, чтобы раскрыть его состояние и публичные методы: Обертка ObservableObject Следующее расширение также будет весьма кстати: И использовать состояния в View: Теперь, когда у нас в распоряжении @Published var state для последующего использования, мы можем выбрать, как это сделать — как StateObject или ObservedObject. Этот пример также иллюстрирует два случая применения, когда мы можем запрашивать свойства состояния напрямую по viewModel.state.something или через @State var, когда нам нужно, чтобы это свойство вело себя как State. iOS использует изменения состояния. Последний шаг миграции завершен. В этой статье мы узнали, как осуществить миграцию рабочей архитектуры платформы в проект KMM, чтобы воспользоваться преимуществами философии совместного использования кода. Мы также глубоко погрузились внутрь библиотеки Orbit Multiplatform swift-gradle-plugin и поняли, какие классы генерируются, их назначение и как они работают вместе. Скоро в OTUS состоится открытое занятие по теме «Одновременная реализация фич на iOS + Android. Необходимый tool-set». На вебинаре обсудим мультиплатформенную разработку для iOS и Android, а также рассмотрим технологию Kotlin-Multiplatform с точки зрения Swift разработчика. Регистрация для всех желающих доступна по ссылке.  Переводчик, редактор