Spec-Zone.ru › Kotlin 1.7

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

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

Плагин Gradle для Multiplatform автоматически добавляет зависимость к платформенно-специфическим (iOS и Android) частям kotlinx.coroutines.

kotlinx.serialization

Для kotlinx.serilization, вам необходим плагин, требуемый системой сборки. Плагин Kotlin serialization поставляется с дистрибутивом компилятора Kotlin, а плагин IntelliJ IDEA включён в плагин Kotlin.

Вы можете настроить плагин сериализации с помощью плагина Kotlin, добавив эту строку в блок plugins в самом начале файла build.gradle в общем модуле:

plugins {
    // 
    kotlin("plugin.serialization") version "1.7.20"
}

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.1.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")
        }
    }
}

Создание запросов к 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 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>

Обновление приложений 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. В 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"
                                }
                            }
                        }
                        Greeting(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. Запустите приложения iOS и Android из Android Studio и убедитесь, что логика вашего приложения синхронизирована:

    Final results

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

Теперь пришло время завершить свой проект и посмотреть, что дальше.

См. также

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

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

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

Получить помощь

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

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

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

Spec-Zone.ru

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