Обновление вашего приложения
Вы уже реализовали общую логику с использованием внешних зависимостей. Теперь вы можете добавить более сложную логику. Запросы к сети и сериализация данных являются наиболее популярными случаями для совместного использования с Kotlin Multiplatform. Изучите, как реализовать их в своем первом приложении, чтобы после завершения этого процесса адаптации вы могли использовать их в будущих проектах.
Обновленное приложение будет получать данные из Интернета с помощью API SpaceX и отображать дату последнего успешного запуска ракеты SpaceX.
Добавление дополнительных зависимостей
В вашем проекте потребуются следующие многоплатформенные библиотеки:
kotlinx.coroutines, для использования сопрограмм для написания асинхронного кода, что позволяет выполнять одновременные операции.kotlinx.serialization, для десериализации JSON-ответов в объекты сущностных классов, используемых для обработки операций с сетью.Ktor, фреймворк в качестве HTTP-клиента для получения данных из Интернета.
kotlinx.coroutines
Чтобы добавить kotlinx.coroutines в свой проект, укажите зависимость в наборе исходных файлов common. Для этого добавьте следующую строку в файл build.gradle.kts общего модуля:
sourceSets {
val commonMain by getting {
dependencies {
// ...
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
}
}
}
Плагин 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, добавив эту строку в существующий блок plugins в самом начале файла build.gradle.kts в общем модуле:
plugins {
// ...
kotlin("plugin.serialization") version "1.9.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.3.2"
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 getting {
// ...
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.
-
В функции
greet(), получите информацию о запусках ракет, вызвав методhttpClient.get()и найдите последний запуск:import io.ktor.client.call.* import io.ktor.client.request.* class Greeting { // ... @Throws(Exception::class) suspend fun greet(): 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в функцииgreet()необходим, так как она теперь содержит вызов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 к функции greet(). Теперь необходимо обновить нативные (iOS, Android) части проекта, чтобы они могли правильно обработать результат вызова функции greet().
Приложение Android
Поскольку и общий модуль, и приложение Android написаны на Kotlin, использование общего кода из Android является простым:
В androidApp/src/main/java, найдите файл MainActivity.kt и обновите указанный класс, заменив предыдущую реализацию:
import androidx.compose.runtime.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
var text by remember { mutableStateOf("Loading") }
LaunchedEffect(true) {
text = try {
Greeting().greet()
} catch (e: Exception) {
e.localizedMessage ?: "error"
}
}
GreetingView(text)
}
}
}
}
}
Функция greet() теперь вызывается внутри 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используется для подписки на модель представления.
Теперь модель представления будет испускать сигналы всякий раз, когда это свойство изменяется.
-
Вызовите функцию
greet(), которая теперь также загружает данные с API SpaceX и сохраняет результат в свойствеtext:class ViewModel: ObservableObject { @Published var text = "Loading..." init() { Greeting().greet { 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).Функция
greet()была помечена аннотацией@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