Spec-Zone.ru › Kotlin 1.8

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

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

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

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

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

  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 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()
    
  5. Осталось импортировать randomUUID в файл LoginDataSource.kt каталога shared/src/commonMain.

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

    import com.jetbrains.simplelogin.shared.randomUUID
    

Запустите ваше кроссплатформенное приложение на 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

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

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

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

END_OF_DOCUMENT_MARKER

Что дальше?

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

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

  • Добавить зависимости для Android

  • Добавить зависимости для iOS

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

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

Последнее изменение: 10 января 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