Spec-Zone.ru › Kotlin 1.9

Создание Android-приложения для работы на iOS – руководство

Узнайте, как сделать ваше существующее Android-приложение кроссплатформенным, чтобы оно работало как на Android, так и на iOS. Вам нужно будет написать код и протестировать его для Android и iOS только один раз, в одном месте.

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

Если вы не знакомы с Kotlin Multiplatform для мобильных устройств, сначала изучите, как настроить среду и создать кроссплатформенное приложение с нуля.

Подготовка среды разработки

  1. Установите все необходимые инструменты и обновите их до последних версий.

    Для выполнения определенных шагов в этом руководстве, которые включают написание кода, специфичного для iOS, и запуск iOS-приложения, вам потребуется Mac с macOS. Эти шаги нельзя выполнить на других операционных системах, таких как Microsoft Windows. Это связано с требованиями Apple.

  2. В Android Studio создайте новый проект из системы управления версиями:

    https://github.com/Kotlin/kmm-integration-sample
    

    Ветвь master содержит начальное состояние проекта — простое Android-приложение. Чтобы увидеть конечное состояние с iOS-приложением и общим модулем, переключитесь на ветвь final.

  3. Переключитесь на представление Проект.

    Project view

Сделайте свой код кроссплатформенным

Чтобы ваше приложение работало на iOS, вы сначала сделаете свой код кроссплатформенным, а затем повторно используете этот кроссплатформенный код в новом iOS-приложении.

Чтобы сделать ваш код кроссплатформенным:

  1. Определите, какой код сделать кроссплатформенным.

  2. Создайте общий модуль для кроссплатформенного кода.

  3. Добавьте зависимость от общего модуля в ваше Android-приложение.

  4. Сделайте бизнес-логику кроссплатформенной.

  5. Запустите ваше кроссплатформенное приложение на Android.

Определите, какой код сделать кроссплатформенным

Определите, какой код вашего Android-приложения лучше всего использовать совместно для iOS, а какой оставить нативными. Простое правило: используйте совместно то, что хотите повторно использовать как можно больше. Бизнес-логика часто одинакова для Android и iOS, поэтому это отличный кандидат для повторного использования.

В вашем примере Android-приложения бизнес-логика хранится в пакете com.jetbrains.simplelogin.androidapp.data. Ваше будущее iOS-приложение будет использовать ту же логику, поэтому вы должны сделать ее кроссплатформенной.

Business logic to share

Создайте общий модуль для кроссплатформенного кода

Кроссплатформенный код, используемый как для iOS, так и для Android, хранится в общем модуле. Плагин Kotlin Multiplatform предоставляет специального мастера для создания таких модулей.

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

  1. В Android Studio нажмите Файл | Новый | Новый Модуль.

  2. В списке шаблонов выберите Общий модуль Kotlin Multiplatform, введите имя модуля shared, и выберите Стандартный фреймворк в списке вариантов распространения iOS-фреймворка.
    Это необходимо для подключения общего модуля к iOS-приложению.

    Kotlin Multiplatform shared module
  3. Нажмите Готово.

Мастер создаст общий модуль Kotlin Multiplatform, обновит конфигурационные файлы и создаст файлы с классами, демонстрирующими преимущества Kotlin Multiplatform. Вы можете узнать больше о структуре проекта.

Добавление зависимости от общего модуля в ваше Android-приложение

Чтобы использовать кроссплатформенный код в вашем Android-приложении, подключите общий модуль к нему, переместите код бизнес-логики туда и сделайте этот код кроссплатформенным.

  1. В файле build.gradle.kts общего модуля убедитесь, что compileSdk и minSdk совпадают с теми, что в build.gradle.kts вашего Android-приложения в модуле app.

    Если они отличаются, обновите их в build.gradle.kts общего модуля. В противном случае возникнет ошибка компиляции.

  2. Добавьте зависимость от общего модуля в build.gradle.kts вашего Android-приложения.

    dependencies {
        implementation (project(":shared"))
    }
    
  3. Синхронизируйте файлы Gradle, нажав Синхронизировать сейчас в уведомлении.

    Synchronize the Gradle files
  4. В каталоге app/src/main/java/ откройте класс LoginActivity в пакете com.jetbrains.simplelogin.androidapp.ui.login.

  5. Чтобы убедиться, что общий модуль успешно подключен к вашему приложению, выведите результат функции greet() в журнал, обновив метод onCreate().

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        Log.i("Login Activity", "Hello from shared module: " + (Greeting().greet()))
    }
    
  6. Следуйте рекомендациям Android Studio по импорту отсутствующих классов.

  7. Отладьте app. На вкладке Logcat найдите Hello в журнале, и вы найдете приветствие из общего модуля.

    Greeting from the shared module

Сделайте бизнес-логику кроссплатформенной

Теперь вы можете выделить код бизнес-логики в общий модуль Kotlin Multiplatform и сделать его независимым от платформы. Это необходимо для повторного использования кода как на Android, так и на iOS.

  1. Переместите код бизнес-логики com.jetbrains.simplelogin.androidapp.data из каталога app в пакет com.jetbrains.simplelogin.shared в каталоге shared/src/commonMain. Вы можете перетащить пакет или переструктурировать его, перенеся всё из одного каталога в другой.

    Drag and drop the package with the business logic code
  2. Когда Android Studio спросит, что вы хотите сделать, выберите перемещение пакета и подтвердите переструктурирование.

    Refactor the business logic package
  3. Проигнорируйте все предупреждения о платформенно-зависимом коде и нажмите Продолжить.

    Warnings about platform-dependent code
  4. Удалите код, специфичный для Android, заменив его кроссплатформенным Kotlin-кодом или подключившись к API, специфичным для Android, используя expect и actual объявления. Подробности см. в следующих разделах:

Замените код, специфичный для Android, кроссплатформенным кодом

Чтобы ваш код работал как на Android, так и на iOS, замените все зависимости JVM на зависимости Kotlin в перемещённом каталоге data где это возможно.

  1. В классе LoginDataSource замените IOException в функции login() на RuntimeException. IOException недоступен в Kotlin.

    // Before
    return Result.Error(IOException("Error logging in", e))
    
    // After
    return Result.Error(RuntimeException("Error logging in", e))
    
  2. В классе LoginDataValidator замените класс Patterns из пакета android.utils на регулярное выражение Kotlin для проверки адресов электронной почты:

    // Before
    private fun isEmailValid(email: String) = Patterns.EMAIL_ADDRESS.matcher(email).matches()
    
    // After
    private fun isEmailValid(email: String) = emailRegex.matches(email)
    
    companion object {
        private val emailRegex = 
            ("[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
                "\\@" +
                "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
                "(" +
                "\\." +
                "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
                ")+").toRegex()
    }
    

Подключение к платформенно-зависимым API из кроссплатформенного кода

В классе LoginDataSource генерируется универсальный уникальный идентификатор (UUID) для fakeUser с помощью класса java.util.UUID, который недоступен для iOS.

val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")

Так как стандартная библиотека Kotlin не предоставляет функций для генерации UUID, в этом случае вам всё ещё нужно использовать платформенно-зависимые функции.

Предоставьте объявление expect для функции randomUUID() в общем коде и его actual реализации для каждой платформы — Android и iOS — в соответствующих наборах исходных файлов. Подробнее о подключении к платформенно-зависимым API.

  1. Удалите класс java.util.UUID из общего кода:

    val fakeUser = LoggedInUser(randomUUID(), "Jane Doe")
    
  2. Создайте файл Utils.kt в пакете com.jetbrains.simplelogin.shared каталога shared/src/commonMain и укажите объявление expect.

    package com.jetbrains.simplelogin.shared
    
    expect fun randomUUID(): String
    
  3. Создайте файл Utils.kt в пакете com.jetbrains.simplelogin.shared каталога shared/src/androidMain и укажите реализацию actual для randomUUID() в Android:

    package com.jetbrains.simplelogin.shared
    
    import java.util.*
    
    actual fun randomUUID() = UUID.randomUUID().toString()
    
  4. Создайте файл Utils.kt в com.jetbrains.simplelogin.shared каталога shared/src/iosMain и укажите реализацию actual для randomUUID() в iOS:

    package com.jetbrains.simplelogin.shared
    
    import platform.Foundation.NSUUID
    
    actual fun randomUUID(): String = NSUUID().UUIDString()
    
  5. Осталось только явно импортировать randomUUID в файл LoginDataSource.kt каталога shared/src/commonMain:

    import com.jetbrains.simplelogin.shared.randomUUID
    

    Для Android и iOS Kotlin будет использовать свои различные платформенно-зависимые реализации.

Запустите своё кроссплатформенное приложение на Android

Запустите своё кроссплатформенное приложение на Android, чтобы убедиться в его работоспособности.

Android login application
END_OF_DOCUMENT_MARKER

Настройка кроссплатформенного приложения для работы на iOS

После настройки кроссплатформенного приложения для Android, вы можете создать приложение для iOS и повторно использовать общую бизнес-логику.

  1. Создать проект iOS в Xcode.

  2. Подключить фреймворк к вашему проекту iOS.

  3. Использовать общий модуль из Swift.

Создание проекта iOS в Xcode

  1. В Xcode, нажмите Файл | Новый | Проект.

  2. Выберите шаблон приложения iOS и нажмите Далее.

    iOS project template
  3. В качестве имени продукта укажите simpleLoginIOS и нажмите Далее.

    iOS project settings
  4. В качестве расположения проекта выберите каталог, в котором хранится ваше кроссплатформенное приложение, например, kmm-integration-sample.

В Android Studio вы получите следующую структуру:

iOS project in Android Studio

Для согласованности с другими верхнеуровневыми каталогами вашего кроссплатформенного проекта, вы можете переименовать каталог simpleLoginIOS в iosApp.

Renamed iOS project directory in Android Studio

Подключение фреймворка к проекту iOS

После получения фреймворка, вы можете подключить его к вашему проекту iOS вручную.

Альтернативный вариант — настроить интеграцию через CocoaPods, но эта интеграция выходит за рамки данного руководства.

Вручную подключите фреймворк к проекту iOS:

  1. В Xcode откройте настройки проекта, дважды щелкнув имя проекта.

  2. На вкладке Фазы сборки настроек проекта нажмите + и добавьте Новую фазу сценария выполнения.

    Add run script phase
  3. Добавьте следующий скрипт:

    cd "$SRCROOT/.."
    ./gradlew :shared:embedAndSignAppleFrameworkForXcode
    
    Add the script
  4. Переместите фазу Сценарий выполнения перед фазой Компиляция исходных кодов.

    Move the Run Script phase
  5. На вкладке Настройки сборки переключитесь на Все настройки сборки и укажите Пути поиска фреймворков в разделе Пути поиска:

    $(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)
    
    Framework search path
  6. На вкладке Настройки сборки укажите Другие флаги компоновщика в разделе Компоновка:

    $(inherited) -framework shared
    
    Linker flag
  7. Соберите проект в Xcode. При правильной настройке проект будет успешно собран.

Если у вас есть настройка сборки, отличная от стандартной Debug или Release, на вкладке Настройки сборки добавьте настройку KOTLIN_FRAMEWORK_BUILD_TYPE в раздел Пользовательские и установите значение Debug или Release.

Использование общего модуля из Swift

  1. В Xcode откройте файл ContentView.swift и импортируйте модуль shared.

    import shared
    
  2. Для проверки корректной связи, используйте функцию greet() из общего модуля вашего кроссплатформенного приложения:

    import SwiftUI
    import shared
    
    struct ContentView: View {
        var body: some View {
            Text(Greeting().greet())
            .padding()
        }
    }
    
    Greeting from the shared module
  3. В ContentView.swift, напишите код для использования данных из общего модуля и отрисовки пользовательского интерфейса приложения:

    import SwiftUI import shared struct ContentView: View { @State private var username: String = "" @State private var password: String = "" @ObservedObject var viewModel: ContentView.ViewModel var body: some View { VStack(spacing: 15.0) { ValidatedTextField(titleKey: "Имя пользователя", secured: false, text: $username, errorMessage: viewModel.formState.usernameError, onChange: { viewModel.loginDataChanged(username: username, password: password) }) ValidatedTextField(titleKey: "Пароль", secured: true, text: $password, errorMessage: viewModel.formState.passwordError, onChange: { viewModel.loginDataChanged(username: username, password: password) }) Button("Войти") { viewModel.login(username: username, password: password) }.disabled(!viewModel.formState.isDataValid || (username.isEmpty && password.isEmpty)) } .padding(.all) } } struct ValidatedTextField: View { let titleKey: String let secured: Bool @Binding var text: String let errorMessage: String? let onChange: () -> () @ViewBuilder var textField: some View { if secured { SecureField(titleKey, text: $text) } else { TextField(titleKey, text: $text) } } var body: some View { ZStack { textField .textFieldStyle(RoundedBorderTextFieldStyle()) .autocapitalization(.none) .onChange(of: text) { _ in onChange() } if let errorMessage = errorMessage { HStack { Spacer() FieldTextErrorHint(error: errorMessage) }.padding(.horizontal, 5) } } } } struct FieldTextErrorHint: View { let error: String @State private var showingAlert = false var body: some View { Button(action: { self.showingAlert = true }) { Image(systemName: "exclamationmark.triangle.fill") .foregroundColor(.red) } .alert(isPresented: $showingAlert) { Alert(title: Text("Ошибка"), message: Text(error), dismissButton: .default(Text("Понятно!"))) } } } extension ContentView { struct LoginFormState { let usernameError: String? let passwordError: String? var isDataValid: Bool { get { return usernameError == nil && passwordError == nil } } } class ViewModel: ObservableObject { @Published var formState = LoginFormState(usernameError: nil, passwordError: nil) let loginValidator: LoginDataValidator let loginRepository: LoginRepository init(loginRepository: LoginRepository, loginValidator: LoginDataValidator) { self.loginRepository = loginRepository self.loginValidator = loginValidator } func login(username: String, password: String) { if let result = loginRepository.login(username: username, password: password) as? ResultSuccess { print("Успешная авторизация. Добро пожаловать, \(result.data.displayName)") } else { print("Ошибка при авторизации") } } func loginDataChanged(username: String, password: String) { formState = LoginFormState( usernameError: (loginValidator.checkUsername(username: username) as? LoginDataValidator.ResultError)?.message, passwordError: (loginValidator.checkPassword(password: password) as? LoginDataValidator.ResultError)?.message) } } }
  4. В simpleLoginIOSApp.swift, импортируйте модуль shared и укажите аргументы для функции ContentView().

    import SwiftUI
    import shared
    
    @main
    struct SimpleLoginIOSApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView(viewModel: .init(loginRepository: LoginRepository(dataSource: LoginDataSource()), loginValidator: LoginDataValidator()))
            }
        }
    }
    
Simple login application

Получите результат – обновляйте логику только один раз

Теперь ваше приложение кроссплатформенное. Вы можете обновить бизнес-логику в одном месте и увидеть результаты как на Android, так и на iOS.

  1. В Android Studio измените логику валидации пароля пользователя в функции checkPassword() класса LoginDataValidator.

    package com.jetbrains.simplelogin.shared.data
    
    class LoginDataValidator {
    //...
        fun checkPassword(password: String): Result {
            return when {
                password.length < 5 -> Result.Error("Password must be >5 characters")
                password.lowercase() == "password" -> Result.Error("Password shouldn't be \"password\"")
                else -> Result.Success
            }
        }
    //...
    }
    
  2. Запустите приложения iOS и Android из Android Studio, чтобы увидеть изменения:

    iOS run configuration
    iOS application password error
    Android application password error

Вы можете ознакомиться с кодом руководства.

Что еще можно разделить?

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

Что дальше?

После того, как вы сделали свое Android-приложение кроссплатформенным, вы можете перейти к:

  • Добавление зависимостей от кроссплатформенных библиотек

  • Добавление зависимостей Android

  • Добавление зависимостей iOS

Вы также можете ознакомиться с ресурсами сообщества:

  • Видео: 3 способа подготовки кода Kotlin JVM для Kotlin Multiplatform Mobile

Последнее изменение: 11 мая 2023
Понимание структуры мобильного проекта Публикация приложения

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/multiplatform-mobile-integrate-in-existing-app.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API