Spec-Zone.ru › Kotlin 1.7

Превратите ваше Android-приложение в приложение для iOS – учебник

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

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

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

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

  1. Установите Android Studio 4.2 или Android Studio 2020.3.1 Canary 8 или выше и другие инструменты для кроссплатформенной разработки мобильных приложений на macOS.

    Для выполнения определенных шагов в этом руководстве, включая написание кода для 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 Mobile предоставляет специальный мастер для создания таких модулей.

В своем проекте 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 вашего Android-приложения в модуле app.

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

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

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

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

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

    override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
    
       Log.i("Login Activity", "Hello from shared module: " + (Greeting().greeting()))
    
    }
    
  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()
    

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

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

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

Android login application

Настройка работы кроссплатформенного приложения на 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. Для проверки правильности подключения, используйте функцию greeting() из общего модуля вашего кроссплатформенного приложения:

    import SwiftUI
    import shared
    
    struct ContentView: View {
        var body: some View {
            Text(Greeting().greeting())
            .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. Обновите gradle.properties для подключения вашего приложения iOS к Android Studio, чтобы запускать его на эмуляторе или реальном устройстве прямо там:

    xcodeproj=iosApp/SimpleLoginIOS.xcodeproj
    
  3. Синхронизируйте файлы Gradle, нажав Синхронизировать сейчас в сообщении об ошибке.

    Synchronize the Gradle files

Вы увидите новую конфигурацию запуска simpleLoginIOS для запуска вашего приложения iOS прямо из Android Studio.

iOS run configuration
iOS application password error
Android application password error

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

END_OF_DOCUMENT_MARKER

Что еще можно поделиться?

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

Что дальше?

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

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

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

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

  • Изучение асинхронности

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

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

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

© 2010–2022 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