KA0706 — Подключение к API платформы

  • Запись изменена:08.12.2022
  • Post category:Публикации
  • Reading time:23 минут чтения

Любая технология, направленная на предоставление решения для многоплатформенной разработки, подходит к проблеме обработки различий между платформами с новой точки зрения.

Когда вы пишете программу на языке высокого уровня, таком как C или Java, вы должны скомпилировать ее для работы на такой платформе, как Windows или Linux. Было бы замечательно, если бы компиляторы могли взять один и тот же код и создать форматы, понятные для разных платформ. Однако это легче сказать, чем сделать.

Kotlin Multiplatform использует эту концепцию и обещает запускать практически один и тот же высокоуровневый код на нескольких платформах — например, JVM, JS или нативных платформах, таких как iOS, напрямую.

В отличие от Java, KMP не зависит от виртуальной машины, работающей на целевой платформе. Он предоставляет компиляторы и библиотеки для конкретных платформ, такие как Kotlin/JVM, Kotlin/JS и Kotlin/Native.

В этой главе вы узнаете, как структурировать свой код в соответствии с предложенным KMP подходом к обработке лакомых кусочков, специфичных для платформы.

Повторное использование кода между платформами

Kotlin Multiplatform не компилирует весь общий модуль для всех платформ в целом. Вместо этого определенный объем кода является общим для всех платформ, а определенный объем общего кода специфичен для каждой платформы. Для этого он использует механизм под названием expect/actual .

В главе 1 вы познакомились с этими двумя новыми ключевыми словами. Теперь вы углубитесь в эту концепцию.

Думайте об expectэтом как о прославленном интерфейсе в Kotlin или протоколе в Swift. Вы определяете классы, свойства и функции expect, говоря, что общий общий код ожидает , что что- то будет доступно на всех платформах. Кроме того, вы используете actualдля обеспечения фактической реализации на каждой платформе.

Подобно интерфейсу или протоколу, объекты с тегом expectне включают код реализации. Вот где actualприходит.

После определения ожидаемых объектов их можно легко использовать в общем коде. KMP использует соответствующий компилятор для компиляции кода, который вы написали для каждой платформы. Например, он использует Kotlin/JVM для Android и Kotlin/Native для iOS или macOS. Позже в процессе компиляции каждый из них будет объединен с скомпилированной версией общего кода для соответствующих платформ.

Вы можете спросить, зачем вам это нужно в первую очередь. Иногда вам нужно вызывать методы, специфичные для каждой платформы. Например, вы можете использовать Core ML на платформах Apple или ML Kit на Android для машинного обучения. Вы можете определить определенные ожидаемые классы, методы и свойства в общем коде и обеспечить фактическую реализацию по-разному для каждой платформы.

Механизм ожидаемого/фактического позволяет вам обращаться к собственным библиотекам каждой платформы с помощью Kotlin. Как это круто!

Поздоровайся с организацией

После того, как вы создадите отличное приложение, чтобы найти подходящее время для организации ваших международных встреч, вам понадобится способ делать TODO и напоминания для этих встреч. Организация поможет вам сделать именно это.

Как и во многих приложениях, которые вы используете каждый день, в Organize есть страница, на которой отображается информация об устройстве, на котором работает приложение. Если вы когда-либо сталкивались с ошибкой в ​​своих приложениях, вы знаете, насколько ценной может быть эта информация при отладке.

Откройте стартовый проект для этого раздела. В основном это недавно созданный проект с использованием плагина KMM для Android Studio с уже установленными дополнительными платформами и зависимостями. Основное отличие от проекта, который вы создали в разделе 1, заключается в том, что на этот раз вы собираетесь использовать обычную платформу вместо Cocoapods для распространения платформы iOS. В конце концов, узнавать что-то новое всегда приятно.

Задолго до того, как Apple представила Swift Package Manager , разработчики iOS использовали разные подходы к управлению зависимостями. В то время как Cocoapods был — или до сих пор — де-факто способом управления зависимостями, некоторые люди склонны использовать другие подходы по многим причинам.

Некоторые преимущества использования Cocoapods:

  • Это легко настроить.
  • Он обрабатывает весь процесс создания зависимостей и связывания их с вашим проектом.
  • У него большое сообщество, которое может помочь, если что-то пойдет не так.
  • Практически вы можете найти любую зависимость в их централизованном репозитории. У него также есть веб-сайт для более удобного поиска и поиска.

Некоторые недостатки использования Cocoapods:

  • Это требует установки Ruby на вашем компьютере для разработки или на любом компьютере, который вы используете для непрерывной интеграции. Вам также необходимо определить зависимости в отдельном файле, используя язык Ruby, который многим не нравится.
  • По сути, он берет на себя управление вашим проектом Xcode, добавляя рабочую область и пару сценариев сборки, что может вызвать проблемы по мере увеличения вашего проекта.
  • Обычно это приводит к увеличению времени сборки, поскольку каждый раз, когда вы создаете свой проект, Xcode создает все ваши зависимости.

Если вы когда-нибудь решите создать новый проект самостоятельно, вы можете изменить параметр распространения платформы iOS на следующей странице.

Рис. 6.1. Выбор опции «Обычный фреймворк» для дистрибутива фреймворка iOS

Рис. 6.1. Выбор опции «Обычный фреймворк» для дистрибутива фреймворка iOS

Обновление класса платформы

Как объяснялось ранее, вы собираетесь создать страницу для своих приложений, на которой вы будете отображать информацию об устройстве, на котором запущено приложение.

Если на то пошло, большая часть вашей работы в этой главе относится к Platformклассу.

Структура папок

В Android Studio выберите представление проекта в Project Navigator. Внутри общего модуля проверьте структуру каталогов.

Рис. 6.2 — Структура папок в Android Studio

Рис. 6.2 — Структура папок в Android Studio

Внутри проекта уже есть файл Platform.kt ; точнее, их четыре — по одной на каждую поддерживаемую вами платформу плюс одна. Чтобы механизм ожидаемого/фактического работал, вам необходимо определить сущности expectи actualв одном и том же пакете на каждой платформе.

На изображении выше expectкласс Platform находится внутри пакета com.raywenderlich.organize в каталоге commonMain . Реализации actualдля iOS, Android и рабочего стола находятся внутри одного пакета в iosMain , androidMain и desktopMain соответственно.

Создание класса Platform для модуля Common

Откройте Platform.kt в папке commonMain . Замените expectопределение класса следующим:

expect class Platform() { val osName: String val osVersion: String val deviceModel: String val cpuType: String val screen: ScreenInfo? fun logSystemInfo() } expect class ScreenInfo() { val width: Int val height: Int val density: Int }

Написав это, вы даете KMP обещание предоставить эту информацию. Как видите, здесь нет никакой реализации. Вы определяете, что хотите, точно так же, как interfaceили protocol.

Примечание . Вы использовали сокращенную нотацию Kotlin для определения конструктора с помощью Platform(). Это означает, что теперь KMP ожидает, что вы предоставите реализацию конструктора наряду со свойствами и методами.

Вы можете быть удивлены тем, что не определили Platformили ScreenInfoкак класс данных; в конце концов, эти классы кажутся идеально подходящими для класса данных, поскольку они по сути являются держателями данных.

Причина в том, что классы данных в Kotlin автоматически генерируют некоторые реализации под капотом. Следовательно, вы не можете использовать их здесь, так как ожидаемые классы не должны иметь реализации.

Вы также не можете определять вложенные классы внутри файла expect class. Следовательно, вы определили ScreenInfoкласс вне Platformопределения. Вы также можете создать новый файл, если хотите. Выполнение этого в том же файле тоже сработает.

В поле кода нажмите на желтый ромб с буквой А внутри. Это позволяет вам перейти к фактическому файлу реализации для платформ, которые вы определили в проекте.

Рис. 6.3. Переход к фактическим файлам реализации.

Рис. 6.3. Переход к фактическим файлам реализации.

Если файлы еще не были на своих местах или вы еще не реализовали фактическое определение, вы можете поместить курсор на ожидаемое имя класса и нажать Alt+Enter на клавиатуре. Android Studio поможет облегчить этот процесс. Так обстоит дело ScreenInfo, например:

Рис. 6.4. Alt+Enter при ожидании имени класса для создания фактических классов.

Рис. 6.4. Alt+Enter при ожидании имени класса для создания фактических классов.

Внедрение платформы на Android

Перейдите к Platform.kt внутри папки androidMain .

Вы увидите, что Android Studio уже начала уговаривать вас выполнить обещание. Ведь КМП находится в зачаточном состоянии, а вы знаете, какие бывают малыши!

Рис. 6.5 — Ошибки Android Studio в актуальном классе

Рис. 6.5 — Ошибки Android Studio в актуальном классе

Замените все определение класса этим блоком кода:

`//1
actual class Platform actual constructor() {
//2
actual val osName = «Android»

//3
actual val osVersion = «${Build.VERSION.SDK_INT}»
//4
actual val deviceModel = «${Build.MANUFACTURER} ${Build.MODEL}»

//5
actual val cpuType = Build.SUPPORTED_ABIS.firstOrNull() ?: «—«

//6
actual val screen: ScreenInfo? = ScreenInfo()
//7
actual fun logSystemInfo() {
Log.d(
«Platform»,
«($osName; $osVersion; $deviceModel; ${screen!!.width}x${screen!!.height}@${screen!!.density}x; $cpuType)»
)
}
}
// 8
actual class ScreenInfo actual constructor() {
//9
private val metrics = Resources.getSystem().displayMetrics
//10
actual val width = metrics.widthPixels
actual val height = metrics.heightPixels
actual val density = round(metrics.density).toInt()
}`

Это кажется большим количеством кода, но это довольно просто:

  1. Вы предоставляете фактическую реализацию для, Platformа также его конструктор по умолчанию. Здесь вы не можете использовать сокращенную запись, как в ожидаемом файле. Вам нужно явно поместить actualключевое слово перед constructor.
  2. Для имени операционной системы вы указали значение "Android", потому что знаете, что этот код будет скомпилирован для Android-части общего модуля.
  3. Для версии операционной системы вы использовали версию SDK из Buildкласса в Android. Убедитесь, что Android Studio импортирует необходимый пакет: android.os.Build. Поскольку вы находитесь внутри Android-части общего модуля, вы можете свободно использовать любой специфичный для Android API.
  4. Для модели устройства вы использовали статические свойства MANUFACTURER.MODEL``Build
  5. К счастью, с помощью этого свойства Buildможно указать тип процессора устройства . SUPPORTED_ABISПоскольку в некоторых старых версиях Android результат может быть нулевым, укажите также значение по умолчанию.
  6. Вы инициализируете экземпляр ScreenInfoи сохраняете его в screenсвойстве. Вам нужно будет явно написать тип, так как если вы этого не сделаете, тип screenсвойства не будет обнуляемым. Мало того, вы обещали, что это свойство может быть обнулено в ожидаемом файле, и всегда следует придерживаться их обещаний. Причина, по которой это тип, допускающий значение NULL, будет ясна, когда вы реализуете часть рабочего стола.
  7. Как и в interface, здесь вы предоставляете реализацию функции. На данный момент вы будете использовать Logкласс в Android для вывода всех свойств на консоль. Обязательно импортируйте android.util.Log. Вы можете безопасно развернуть свойство, допускающее значение NULL screen, поскольку вы инициализировали его ненулевым значением в предыдущей части.
  8. Вы предоставляете фактическую реализацию для, ScreenInfoа также его конструктор по умолчанию.
  9. Для получения свойств экрана вам понадобится DisplayMetricsобъект. Вы можете получить это, используя этот блок кода. Как видите, у вас могут быть дополнительные свойства или функции внутри реального класса. Обязательно импортируйте android.content.res.Resources.
  10. Вы получаете ширину, высоту и плотность экрана, используя свойство метрики, которое вы определили ранее. Импортируйте kotlin.math.round, чтобы иметь возможность использовать roundфункцию.

Далее вы реализуете код для iOS.

Внедрение платформы на iOS

Когда вы находитесь внутри реального файла, вы можете щелкнуть желтый ромб с буквой E в поле, чтобы перейти к ожидаемому определению. Находясь внутри Platform.kt в папке androidMain , щелкните желтый значок и вернитесь к файлу в общем каталоге. Оттуда щелкните значок A и перейдите к фактическому файлу iOS .

Основы кода, который вы собираетесь добавить, такие же, как и раньше. Однако на этот раз вы обращаетесь к специфичным для iOS платформам, таким как UIKit, Foundationи CoreGraphicsполучаете необходимую информацию.

Замените фактическую реализацию следующим блоком кода.

actual class Platform actual constructor() { //1 actual val osName = when (UIDevice.currentDevice.userInterfaceIdiom) { UIUserInterfaceIdiomPhone -> "iOS" UIUserInterfaceIdiomPad -> "iPadOS" else -> kotlin.native.Platform.osFamily.name } //2 actual val osVersion = UIDevice.currentDevice.systemVersion //3 actual val deviceModel: String get() { memScoped { val systemInfo: utsname = alloc() uname(systemInfo.ptr) return NSString.stringWithCString(systemInfo.machine, encoding = NSUTF8StringEncoding) ?: "---" } } //4 actual val cpuType = kotlin.native.Platform.cpuArchitecture.name //5 actual val screen: ScreenInfo? = ScreenInfo() //6 actual fun logSystemInfo() { NSLog( "($osName; $osVersion; $deviceModel; ${screen!!.width}x${screen!!.height}@${screen!!.density}x; $cpuType)" ) } } actual class ScreenInfo actual constructor() { //7 actual val width = CGRectGetWidth(UIScreen.mainScreen.nativeBounds).toInt() actual val height = CGRectGetHeight(UIScreen.mainScreen.nativeBounds).toInt() actual val density = UIScreen.mainScreen.scale.toInt() }

  1. UIKitЕсть вызываемый класс, UIDeviceиз которого вы можете запросить информацию о файле currentDevice. В этом коде вы запрашиваете идиому интерфейса, чтобы различать iOS и iPadOS. У UIUserInterfaceIdiomперечисления есть еще пара случаев. Для краткости вы использовали Platformкласс Kotlin/Native для поиска информации в блоке else.
  2. Вы также можете использовать UIDeviceдля получения версии ОС.
  3. Это, безусловно, самый неуклюжий фрагмент кода, который вы встретите в этой книге. Но не волнуйтесь: КМП обычно не такой. Он здесь, чтобы показать вам, где все может пойти не так. Objective-C по своей сути — это C. В платформах Apple есть несколько старых API, которые восходят к 1990-м годам. Поскольку они низкоуровневые и люди не используют их часто, Apple никогда не обновляла их, чтобы иметь более приятный интерфейс. Одной из них является структура C, называемая utsname. Поклонники Unix, ликуйте! Здесь вы вызываете функцию C через Kotlin/Native. Насколько это безумно? В этом блоке кода вы выделяете память, используя memScopedблок и alloc()вызов функции. Затем вы передаете указатель на выделенное пространство памяти unameфункции, которая получает информацию об операционной системе и заполняет ее внутриsystemInfo. Затем вы преобразуете строку C, заполненную именем машины, NSStringи возвращаете ее. Преобразование из NSStringв Kotlin String выполняется автоматически. Фу!
  4. Чтобы получить тип процессора, вы можете еще раз покопаться в коде C или, как здесь, просто использовать Platformкласс Kotlin/Native.
  5. Вы инициализируете экземпляр ScreenInfoи сохраняете его в screenсвойстве. Как и в случае с Android, убедитесь, что вы указали тип явно.
  6. Реализация функции в основном такая же, как и в Android, за исключением того, что вы передаете ту же строку в NSLogфункцию.
  7. Вам нужно объединить свои знания UIKitи CoreGraphicsполучить свойства экрана. Во-первых, вы используете UIScreenкласс для получения информации об mainScreenустройстве. Затем вы используете функции CGRectGetWidthи для извлечения ширины и высоты из свойств, которые являются структурой Objective-C.CGRectGetHeight``CoreGraphics``nativeBounds``CGSize

Это может выглядеть немного странно для разработчиков Kotlin и Swift. Причина в том, что вы используете номенклатуру Objective-C для сущностей. Вот как работает KMP для платформ Apple. Таким образом, функциональная совместимость создает мост между Kotlin и Objective-C.

Блок кода в Разделе 3 странный даже для разработчиков Swift. Если бы вы написали этот раздел, используя Swift, то также были бы некоторые путешествия в мир C:

let deviceModel: String = { var systemInfo = utsname() uname(&systemInfo) let str = withUnsafePointer(to: &systemInfo.machine.0) { ptr in return String(cString: ptr) } return str }()

Почему Objective-C, а не Swift, спросите вы. Хотя создатели KMP работают над функциональной совместимостью Swift, они выбрали Objective-C по нескольким причинам:

  1. Многие фреймворки iOS сами по себе построены на Objective-C. Даже когда вы пишете код на Swift, вы используете мост.
  2. Objective-C имеет более гибкую и динамичную среду выполнения, чем Swift. По-видимому, более строгие функции безопасности типов Swift затруднили бы создание совместимости KMP с технологиями Apple.

Однако использование Objective-C вместо Swift имеет некоторые проблемы. Например, вы не можете использовать некоторые недавно представленные фреймворки, Combineпоскольку они предназначены только для Swift. Кроме того, вы не можете использовать только функции или свойства расширения Swift — или даже случаи перечисления Swift — также. У вас нет другого выбора, кроме как использовать подробное наименование сущностей в Objective-C.

Реализация платформы на десктопе

Откройте Platform.kt в папке desktopMain .

Замените фактическую реализацию этим блоком:

`actual class Platform actual constructor() {
//1
actual val osName = System.getProperty(«os.name») ?: «Desktop»

//2
actual val osVersion = System.getProperty(«os.version») ?: «—«

//3
actual val deviceModel = «Desktop»

//4
actual val cpuType = System.getProperty(«os.arch») ?: «—«

//5
actual val screen: ScreenInfo? = null
//6
actual fun logSystemInfo() {
print(«($osName; $osVersion; $deviceModel; $cpuType)»)
}
}
actual class ScreenInfo actual constructor() {
//7
actual val width = 0
actual val height = 0
actual val density = 0
}`

  1. Настольное приложение основано на JVM. В результате вы можете использовать классы и методы JDK для получения информации об устройстве. В Java есть класс, который называется System. Вы можете получить имя операционной системы, используя статический getPropertyметод с "os.name"параметром. Поскольку этот метод может возвращать значение null, вы предоставили результат по умолчанию.
  2. Вы используете тот же метод, что и раньше, но на этот раз с "os.version"параметром.
  3. Вы жестко кодируете значение "Desktop". JVM не дает возможности узнать что-либо о производителе и модели.
  4. Еще раз, Systemкласс в помощь! Использовать "os.arch"в качестве параметра.
  5. Вы предоставляете null в качестве значения для ScreenInfo. Вы скоро узнаете, почему.
  6. Затем вы используете printфункцию Kotlin для вывода обычной информации на консоль. На этот раз, однако, нет никакой информации о screen.
  7. Чтобы отключить компилятор, вы устанавливаете 0 в свойствах ScreenInfoрабочего модуля. Это не жизненно важно, поскольку вы даже не используете этот класс.

Вы сделали пару странных вещей здесь. Самое главное, вы не предоставили полезную реальную реализацию для ScreenInfo.

В Java нет инструментария пользовательского интерфейса. Если владелец платформы хочет использовать JVM и предоставить разработчикам способ разработки пользовательских интерфейсов, он создает набор инструментов пользовательского интерфейса или использует уже доступный.

Возможно, вы слышали о Swing или Abstract Window Toolkit (AWT). Jetpack Compose for Desktop использует Swing внутри для создания настольных оконных приложений. На момент написания этой книги Jetpack Compose for Desktop не предоставлял возможности запрашивать информацию об экране за пределами компонуемых методов.

Вы также не можете использовать методы Swing или AWT непосредственно в разделе рабочего стола общего модуля. Проблема в том, что этот модуль использует Android Gradle Plugin . Поскольку у Android есть собственный набор инструментов пользовательского интерфейса, и из-за того, как Android использует Java, нет возможности использовать javaплагин вместе с Android Gradle Plugin . Поэтому здесь нельзя использовать методы AWT или Swing.

Эта проблема уже давно указана в Google Issue Tracker и Jetbrains YouTrack , и, к сожалению, для нее пока нет решения.

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

Примечание . Поскольку подключаемый модуль Android Gradle применяется ко всему модулю, среда IDE позволяет импортировать классы, связанные с Android, такие как Logили DisplayMetricsдаже внутри папки рабочего стола . Однако их использование приведет к сбою вашего настольного приложения, поскольку они недоступны во время выполнения.

Делимся дополнительным кодом

Вы могли заметить, что этот logSystemInfoметод практически использует одну и ту же строку снова и снова. Чтобы избежать такого дублирования кода, вы обратитесь к функциям расширения Kotlin.

Откройте Platform.kt в папке commonMain . Как вы знаете, вы не можете добавить реализацию к определенным здесь свойствам или функциям. Однако никто не говорил, что вы не можете использовать функции расширения Kotlin.

В конце файла добавьте это:

val Platform.deviceInfo: String get() { var result = "($osName; $osVersion; $deviceModel; " screen?.let { result += "${it.width}x${it.height}@${it.density}x; " } result += "$cpuType)" return result }

Вы создаете ту же строку, но на этот раз на основании того факта, что screenона может быть нулевой.

Теперь вернитесь к реальным файлам и используйте это свойство внутри logSystemInfoфункций.

В Platform.kt внутри androidMain :

actual fun logSystemInfo() { Log.d("Platform", deviceInfo) }

В Platform.kt внутри iosMain :

actual fun logSystemInfo() { NSLog(deviceInfo) }

В Platform.kt внутри desktopMain :

actual fun logSystemInfo() { print(deviceInfo) }

С помощью этого метода вы можете совместно использовать код между реальными реализациями.

Обновление пользовательского интерфейса

Теперь, когда класс Platform готов, вы завершили свою работу внутри общего модуля. KMP позаботится о создании фреймворков и библиотек, которые вы сможете использовать на каждой поддерживаемой вами платформе. Теперь вы готовы создавать красивые пользовательские интерфейсы на Android, iOS и настольных компьютерах.

Андроид

Вы будете выполнять все свои задачи внутри модуля androidApp . Базовая структура приложения готова для вас. Некоторые важные файлы нуждаются в объяснении. Они также помогут вам в следующих главах. Вот как это выглядит:

Рис. 6.6 – Структура папок для Android-приложения

Рис. 6.6 – Структура папок для Android-приложения

Внутри корневой папки есть AppScaffold.kt и AppNavHost.kt . Эти два файла настраивают экраны приложения и заставляют навигацию между ними работать должным образом. Пожалуйста, не стесняйтесь взглянуть, если вы заинтересованы.

Приложение имеет два основных экрана: RemindersView , на котором сейчас отображается простое «Hello World», и AboutView , который вы собираетесь настроить в этой главе. Иди и открой его.

Здесь ContentView — это место, где происходит все важное. Замените его реализацию следующим фрагментом:

`@Composable
private fun ContentView() {
val items = makeItems()

LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
items(items) { row ->
RowView(title = row.first, subtitle = row.second)
}
}
}`

Здесь вы получаете элементы, которые хотите отобразить, из функции makeItemsи помещаете их в элемент LazyColumn, который в основном представляет собой представление списка. Import Modifierи LazyColumnиз fillMaxSizeбиблиотеки Compose . Добавьте следующий импорт для itemsметода:

import androidx.compose.foundation.lazy.items

Вы будете реализовывать в RowViewближайшее время.

Добавьте makeItemsниже ContentView:

private fun makeItems(): List<Pair<String, String>> { //1 val platform = Platform() //2 val items = mutableListOf( Pair("Operating System", "${platform.osName} ${platform.osVersion}"), Pair("Device", platform.deviceModel), Pair("CPU", platform.cpuType) ) //3 platform.screen?.let { val max = max(it.width, it.height) val min = min(it.width, it.height) items.add(Pair("Display", "${max}×${min} @${it.density}x")) } return items }

  1. Во-первых, вы инициализируете экземпляр Platformсозданного ранее класса.
  2. Затем вы создаете пары данных с заголовками и информацией из platformи сохраняете их в изменяемый список.
  3. Хотя вы знаете, что screenсвойство не равно null на Android, лучше перестраховаться, чем потом сожалеть, сталкиваясь со свойствами, допускающими значение null.

Импорт maxи minиз kotlin.math. И для последней части этого приложения добавьте RowViewкомпонуемую функцию следующим образом:

@Composable private fun RowView( title: String, subtitle: String, ) { Column(modifier = Modifier.fillMaxWidth()) { Column(Modifier.padding(8.dp)) { Text( text = title, style = MaterialTheme.typography.caption, color = Color.Gray, ) Text( text = subtitle, style = MaterialTheme.typography.body1, ) } Divider() } }

Это простая вертикальная стопка текстовых элементов, которые показывают заголовок и подзаголовок. Вы можете использовать предопределенные значения типографики материала, чтобы довести дело до ума. Они аналогичны предопределенным стилям текста в функции динамического ввода iOS.

Это конец вашего путешествия по Android в этой главе. Создайте и запустите приложение и проверьте результат.

Нажмите кнопку i , чтобы просмотреть свойства устройства.

Рис. 6.7 — Первая страница Organize на Android.

Рис. 6.7 — Первая страница Organize на Android.

Рис. 6.8. Страница «Об устройстве» в Organize на Android.

Рис. 6.8. Страница «Об устройстве» в Organize на Android.

Далее вы собираетесь создать приложение для iOS.

iOS

Хотя никто не может помешать вам использовать Android Studio для редактирования файлов Swift, было бы разумнее открыть Xcode.

Внутри папки iosApp в корневом каталоге проекта откройте проект Xcode, дважды щелкнув iosApp.xcodeproj .

Файл ContentView.swift является начальной страницей приложения. Это уже там для вас. Взгляните, если хотите.

Откройте AboutView.swift и замените строку на следующую Text("Hello World"):

AboutListView()

Затем создайте новый файл представления SwiftUI с именем AboutListView , нажав Command-N .

Рис. 6.9 — диалоговое окно создания файла Xcode

Рис. 6.9 — диалоговое окно создания файла Xcode

Сначала импортируйте общий модуль вверху файла:

import shared

Это фреймворк KMP, созданный для вас. Если он выдает ошибку о том, что он не найден, не волнуйтесь. Сборка проекта решит проблему.

Во-вторых, добавьте внутреннюю структуру для хранения данных, которые вы собираетесь показать:

private struct RowItem: Hashable { let title: String let subtitle: String }

Соответствие Hashableпротоколу необходимо для ForEachструктуры в SwiftUI.

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

private let items: [RowItem] = { //1 let platform = Platform() //2 var result: [RowItem] = [ .init( title: "Operating System", subtitle: "\(platform.osName) \(platform.osVersion)" ), .init( title: "Device", subtitle: platform.deviceModel ), .init( title: "CPU", subtitle: platform.cpuType ) ] //3 if let screen = platform.screen { let width = min(screen.width, screen.height) let height = max(screen.width, screen.height) result.append( .init( title: "Display", subtitle: "\(width)×\(height) @\(screen.density)x" ) ) } //4 return result }()

  1. Вы создаете экземпляр Platformкласса.
  2. Затем вы создаете массив RowItemэкземпляров, содержащий информацию из platformэкземпляра.
  3. Затем вы условно разворачиваете screenсвойство, вычисляете widthи heightи добавляете результат в массив.
  4. В конце вы возвращаете массив, который хотите отобразить на странице.

Наконец, замените содержимое bodyсвойства следующим:

var body: some View { List { ForEach(items, id: \.self) { item in VStack(alignment: .leading) { Text(item.title) .font(.footnote) .foregroundColor(.secondary) Text(item.subtitle) .font(.body) .foregroundColor(.primary) } .padding(.vertical, 4) } } }

Это очень простой список в SwiftUI. Для каждого элемента внутри itemsсвойства отображается вертикальная стопка текстовых элементов, состоящая из заголовка и подзаголовка. Вы также применяете множество модификаторов форматирования, таких как fontи foregroundColor, чтобы сделать его более приятным для глаз.

Стройте и запускайте. Затем нажмите кнопку « О программе », чтобы увидеть созданную вами страницу.

Рис. 6.10 — Первая страница Организовать на iOS.

Рис. 6.10 — Первая страница Организовать на iOS.

Рис. 6.11 — Страница «Об устройстве» в Organize на iOS.

Рис. 6.11 — Страница «Об устройстве» в Organize на iOS.

Рабочий стол

В разделе 1 вы узнали, как обмениваться кодом пользовательского интерфейса между Android и настольным компьютером. Чтобы показать, что в этом нет необходимости, вы будете следовать другому подходу к Organize : вы вернетесь к проверенному временем копированию и вставке!

Настройка настольного приложения немного отличается от приложения для Android, хотя они оба используют Jetpack Compose. Одно отличие состоит в том, что вы не используете компонент навигации Jetpack в настольном приложении. Вы также открываете страницу « Об устройстве » в новом окне, чтобы больше соответствовать соглашениям о рабочем столе.

За исключением нескольких нюансов в дизайне страницы «О программе», таких как отображение каждого элемента данных в строке , а не в столбце , код остается прежним. Он есть для вас в стартовом проекте. Откройте AboutView.kt, найдите //2и //3раскомментируйте код под ними.

Существует несколько способов запуска настольного приложения. Вы можете открыть меню Gradle сбоку в разделе desktopApp ▸ создать рабочий стол и нажать « Выполнить » .

Рис. 6.12 — Меню Gradle, запуск настольного приложения

Рис. 6.12 — Меню Gradle, запуск настольного приложения

Приложение работает и очень похоже на приложение для Android.

Рис. 6.13 — Первая страница Организовать на рабочем столе.

Рис. 6.13 — Первая страница Организовать на рабочем столе.

Рис. 6.14. Страница «Об устройстве» приложения «Организовать на рабочем столе».

Рис. 6.14. Страница «Об устройстве» приложения «Организовать на рабочем столе».

Испытание

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

Задача: создать общий регистратор

Вы можете вызывать другие ожидаемые функции внутри ожидаемых/фактических реализаций. Как вы помните, внутри класса была logSystemInfoфункция , где она использовалась и на соответствующей платформе.Platform``NSLog``Log

Рефакторинг этих вызовов в новый класс с именем Logger. В качестве бонуса вы можете добавить уровни логирования в свою реализацию.

Ключевые моменты

  • Вы можете использовать механизм ожидаемого/фактического вызова собственных библиотек каждой платформы с помощью Kotlin.
  • Ожидайте, что сущности ведут себя так же, как interfaceили protocol.
  • На платформах Apple Kotlin использует Objective-C для взаимодействия.
  • Вы можете добавить общую реализацию для ожидаемых объектов с помощью функций расширения Kotlin.