Обновление вашего приложения
Вы уже реализовали общую логику с использованием внешних зависимостей. Теперь вы можете добавить более сложную логику. Запросы к сети и сериализация данных — это наиболее популярные случаи для совместного использования с Kotlin Multiplatform. Изучите, как реализовать их в вашем первом приложении, чтобы после завершения этого обучающего маршрута вы могли использовать их в будущих проектах.
Обновленное приложение будет получать данные из интернета из API SpaceX и отображать дату последнего успешного запуска ракеты SpaceX.
Добавление дополнительных зависимостей
В вашем проекте потребуются следующие многоплатформенные библиотеки:
kotlinx.coroutines, для использования сопрограмм для написания асинхронного кода, что позволяет выполнять операции одновременно.kotlinx.serialization, для десериализации JSON-ответов в объекты сущностных классов, используемых для обработки сетевых операций.Ktor, фреймворк в качестве HTTP-клиента для получения данных из интернета.
kotlinx.coroutines
Чтобы добавить kotlinx.coroutines в ваш проект, укажите зависимость в общем наборе исходных кодов. Для этого добавьте следующую строку в файл build.gradle.kts общего модуля:
sourceSets {
val commonMain by getting {
dependencies {
// ...
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
}
}
}
Плагин Gradle для многоплатформенных приложений автоматически добавляет зависимость к платформенно-специфичным (iOS и Android) частям kotlinx.coroutines.
Если вы используете Kotlin до версии 1.7.20
Если вы используете Kotlin 1.7.20 и выше, у вас уже по умолчанию включен новый менеджер памяти Kotlin/Native. Если это не так, добавьте следующее в конец файла build.gradle.kts:
kotlin.targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java) {
binaries.all {
binaryOptions["memoryModel"] = "experimental"
}
}
kotlinx.serialization
Для kotlinx.serialization, вам нужен плагин, необходимый для системы сборки. Плагин Kotlin serialization поставляется с дистрибутивом компилятора Kotlin, а плагин IntelliJ IDEA включен в плагин Kotlin.
Вы можете настроить плагин сериализации с помощью плагина Kotlin, используя DSL плагинов Gradle, добавив эту строку в существующий блок plugins в самом начале файла build.gradle.kts в общем модуле:
plugins {
//
kotlin("plugin.serialization") version "1.8.0"
}
Ktor
Вы можете добавить Ktor так же, как вы добавили библиотеку kotlinx.coroutines. В дополнение к указанию основной зависимости (ktor-client-core) в общем наборе исходных кодов, вам также необходимо:
Добавить функциональность ContentNegotiation (
ktor-client-content-negotiation), отвечающую за сериализацию/десериализацию содержимого в определенном формате.Добавить зависимость
ktor-serialization-kotlinx-jsonдля того, чтобы указать Ktor на использование формата JSON иkotlinx.serializationв качестве библиотеки сериализации. Ktor будет ожидать JSON-данные и десериализовать их в класс данных при получении ответов.Предоставить платформенные движки, добавив зависимости на соответствующие артефакты в платформенных наборах исходных кодов (
ktor-client-android,ktor-client-darwin).
val ktorVersion = "2.2.1"
sourceSets {
val commonMain by getting {
dependencies {
// ...
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:$ktorVersion")
}
}
val iosMain by creating {
// ...
dependencies {
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
}
}
}
Синхронизируйте файлы Gradle, нажав Синхронизировать сейчас в уведомлении.
Создание запросов к API
Вам понадобится API SpaceX для получения данных и единственного метода для получения списка всех запусков с конечной точки v4/launches.
Добавление модели данных
В shared/src/commonMain/kotlin, создайте новый файл RocketLaunch.kt и добавьте класс данных, который хранит данные из API SpaceX:
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class RocketLaunch (
@SerialName("flight_number")
val flightNumber: Int,
@SerialName("name")
val missionName: String,
@SerialName("date_utc")
val launchDateUTC: String,
@SerialName("success")
val launchSuccess: Boolean?,
)
Класс
RocketLaunchпомечен аннотацией@Serializable, чтобы плагинkotlinx.serializationмог автоматически сгенерировать стандартную сериализацию для него.Аннотация
@SerialNameпозволяет переопределять имена полей, что делает возможным объявлять свойства в классах данных с более удобочитаемыми именами.
Подключение HTTP-клиента
-
В
Greeting.kt, создайте экземпляр KtorHTTPClientдля выполнения сетевых запросов и разбора полученного JSON:import io.ktor.client.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json class Greeting { private val platform: Platform = getPlatform() private val httpClient = HttpClient { install(ContentNegotiation) { json(Json { prettyPrint = true isLenient = true ignoreUnknownKeys = true }) } } }Для десериализации результата запроса GET используется плагин ContentNegotiation Ktor и сериализатор JSON.
-
В функции
greeting(), получите информацию о запусках ракет, вызвав методhttpClient.get()и найдите последний запуск:import io.ktor.client.call.* import io.ktor.client.request.* class Greeting { // ... @Throws(Exception::class) suspend fun greeting(): String { val rockets: List<RocketLaunch> = httpClient.get("https://api.spacexdata.com/v4/launches").body() val lastSuccessLaunch = rockets.last { it.launchSuccess == true } return "Guess what it is! > ${platform.name.reversed()}!" + "\nThere are only ${daysUntilNewYear()} left until New Year! 🎆" + "\nThe last successful launch was ${lastSuccessLaunch.launchDateUTC} 🚀" } }Модификатор
suspendв функцииgreeting()необходим, потому что она теперь содержит вызовget(). Это приостанавливаемая функция, которая имеет асинхронную операцию получения данных из интернета и может вызываться только изнутри сопрограммы или другой приостанавливаемой функции. Сетевой запрос будет выполнен в пуле потоков HTTP-клиента.
Добавление разрешения на доступ к интернету
Для доступа к интернету приложение Android нуждается в соответствующем разрешении. Поскольку все сетевые запросы выполняются из общего модуля, имеет смысл добавить разрешение на доступ к интернету в его манифест.
Обновите свой файл androidApp/src/main/AndroidManifest.xml следующим образом:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jetbrains.simplelogin.kotlinmultiplatformsandbox" >
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
Обновление приложений Android и iOS
Вы уже обновили API общего модуля, добавив модификатор suspend к функции greeting(). Теперь вам нужно обновить нативные (iOS, Android) части проекта, чтобы они могли правильно обрабатывать результат вызова функции greeting().
Приложение Android
Поскольку как общий модуль, так и приложение Android написаны на Kotlin, использование общего кода из Android очень просто:
-
Добавьте библиотеку
kotlinx.coroutinesв приложение Android, добавив строку вbuild.gradle.ktsв папкеandroidApp.dependencies { // .. implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") } Синхронизируйте файлы Gradle, нажав Синхронизировать сейчас в уведомлении.
-
В
androidApp/src/main/java, найдите файлMainActivity.ktи обновите следующий класс, заменив предыдущую реализацию:import androidx.compose.runtime.* import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyApplicationTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background ) { val scope = rememberCoroutineScope() var text by remember { mutableStateOf("Loading") } LaunchedEffect(true) { scope.launch { text = try { Greeting().greeting() } catch (e: Exception) { e.localizedMessage ?: "error" } } } GreetingView(text) } } } } }Функция
greeting()теперь вызывается в сопроцессе внутриLaunchedEffect, чтобы избежать ее повторного вызова при каждой перекомпозиции.
Приложение iOS
Для iOS-части проекта вы будете использовать SwiftUI для построения пользовательского интерфейса и паттерн модель-представление-представление данных для связи пользовательского интерфейса с общим модулем, содержащим всю бизнес-логику.
Модуль уже подключен к проекту iOS — помощник плагина Android Studio выполнил всю настройку. Модуль уже импортирован и используется в ContentView.swift с import shared.
Запустите приложение Xcode и выберите Открыть проект или файл.
Перейдите к своему проекту, например KotlinMultiplatformSandbox, и выберите папку
iosApp. Нажмите Открыть.-
В
iosApp/iosApp.swift, обновите точку входа для вашего приложения:@main struct iOSApp: App { var body: some Scene { WindowGroup { ContentView(viewModel: ContentView.ViewModel()) } } } -
В
iosApp/ContentView.swift, создайте классViewModelдляContentView, который будет подготавливать и управлять данными для него:import SwiftUI import shared struct ContentView: View { @ObservedObject private(set) var viewModel: ViewModel var body: some View { Text(viewModel.text) } } extension ContentView { class ViewModel: ObservableObject { @Published var text = "Loading..." init() { // Data will be loaded here } } }ViewModelобъявлен как расширение дляContentView, так как они тесно связаны.Фреймворк Combine соединяет представление данных (
ContentView.ViewModel) с представлением (ContentView).ContentView.ViewModelобъявлен какObservableObject.Обёртка
@Publishedиспользуется для свойстваtext.Обёртка свойства
@ObservedObjectиспользуется для подписки на представление данных.
Теперь представление данных будет генерировать сигналы всякий раз, когда это свойство меняется.
-
Вызовите функцию
greeting(), которая теперь также загружает данные из API SpaceX и сохраняет результат в свойствеtext:class ViewModel: ObservableObject { @Published var text = "Loading..." init() { Greeting().greeting { greeting, error in DispatchQueue.main.async { if let greeting = greeting { self.text = greeting } else { self.text = error?.localizedDescription ?? "error" } } } } }Kotlin/Native обеспечивает двустороннюю совместимость с Objective-C, поэтому концепции Kotlin, включая функции
suspend, сопоставляются с соответствующими концепциями Swift/Objective-C и наоборот. При компиляции Kotlin-модуля в Apple-фреймворк приостановленные функции доступны в нем как функции с обратными вызовами (completionHandler).Функция
greeting()была помечена аннотацией@Throws(Exception::class). Таким образом, любые исключения, которые являются экземплярами классаExceptionили его подкласса, будут распространяться какNSError, поэтому вы можете обрабатывать их вcompletionHandler.При вызове функций Kotlin
suspendиз Swift обработчики завершения могут вызываться на потоках, отличных от основного, см. интеграцию iOS в менеджере памяти Kotlin/Native. Вот почему используетсяDispatchQueue.main.asyncдля обновления свойстваtext.
-
Перезапустите обе конфигурации androidApp и iosApp в Android Studio, чтобы убедиться, что логика вашего приложения синхронизирована:

Следующий шаг
В заключительной части руководства вы завершите свой проект и увидите, какие шаги предпринять дальше.
См. также
Изучите различные подходы к составлению приостановленных функций.
Узнайте больше о взаимодействии с Objective-C-фреймворками и библиотеками.
Завершите это руководство по сетевому взаимодействию и хранению данных.
Получение помощи
Kotlin Slack. Получите приглашение и присоединитесь к каналу #multiplatform.
Отслеживание проблем Kotlin. Отправить новую проблему.
© 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-upgrade-app.html