Spec-Zone.ru › Kotlin 1.8

Обновление вашего приложения

Вы уже реализовали общую логику с использованием внешних зависимостей. Теперь вы можете добавить более сложную логику. Запросы к сети и сериализация данных — это наиболее популярные случаи для совместного использования с 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-клиента

  1. В Greeting.kt, создайте экземпляр Ktor HTTPClient для выполнения сетевых запросов и разбора полученного 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.

  2. В функции 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>
END_OF_DOCUMENT_MARKER

Обновление приложений Android и iOS

Вы уже обновили API общего модуля, добавив модификатор suspend к функции greeting(). Теперь вам нужно обновить нативные (iOS, Android) части проекта, чтобы они могли правильно обрабатывать результат вызова функции greeting().

Приложение Android

Поскольку как общий модуль, так и приложение Android написаны на Kotlin, использование общего кода из Android очень просто:

  1. Добавьте библиотеку kotlinx.coroutines в приложение Android, добавив строку в build.gradle.kts в папке androidApp.

    dependencies {
        // ..
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
    }
    
  2. Синхронизируйте файлы Gradle, нажав Синхронизировать сейчас в уведомлении.

  3. В 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.

Если вы видите ошибку, что общий модуль не разрешен, запустите приложение.

  1. Запустите приложение Xcode и выберите Открыть проект или файл.

  2. Перейдите к своему проекту, например KotlinMultiplatformSandbox, и выберите папку iosApp. Нажмите Открыть.

  3. В iosApp/iosApp.swift, обновите точку входа для вашего приложения:

    @main
    struct iOSApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView(viewModel: ContentView.ViewModel())
            }
        }
    }
    
  4. В 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 используется для подписки на представление данных.

    Теперь представление данных будет генерировать сигналы всякий раз, когда это свойство меняется.

  5. Вызовите функцию 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.

  6. Перезапустите обе конфигурации androidApp и iosApp в Android Studio, чтобы убедиться, что логика вашего приложения синхронизирована:

    Final results

Следующий шаг

В заключительной части руководства вы завершите свой проект и увидите, какие шаги предпринять дальше.

Перейти к следующей части

См. также

  • Изучите различные подходы к составлению приостановленных функций.

  • Узнайте больше о взаимодействии с Objective-C-фреймворками и библиотеками.

  • Завершите это руководство по сетевому взаимодействию и хранению данных.

Получение помощи

  • Kotlin Slack. Получите приглашение и присоединитесь к каналу #multiplatform.

  • Отслеживание проблем Kotlin. Отправить новую проблему.

Последнее изменение: 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-upgrade-app.html

Spec-Zone.ru

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