Spec-Zone.ru › Kotlin 1.9

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

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

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

Обновление приложений 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, касающиеся общего модуля или при обновлении кода, запустите конфигурацию iosApp из Android Studio.

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

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

    Final results

Вы можете найти это состояние проекта в нашем репозитории GitHub.

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

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

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

См. также

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

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

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

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

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

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

Последнее изменение: 21 июля 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