Kotlin Multiplatform Mobile入門
Projectを作成してSync Project with Gradle Files
したら早速エラー😅
A problem occurred configuring root project 'KmmSandbox'.
> Could not resolve all files for configuration ':classpath'.
> Could not resolve com.android.tools.build:gradle:8.0.2.
Required by:
project : > com.android.application:com.android.application.gradle.plugin:8.0.2
project : > com.android.library:com.android.library.gradle.plugin:8.0.2
> No matching variant of com.android.tools.build:gradle:8.0.2 was found. The consumer was configured to find a library for use during runtime, compatible with Java 8, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '8.0' but:
- Variant 'apiElements' capability com.android.tools.build:gradle:8.0.2 declares a library, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares a component for use during compile-time, compatible with Java 11 and the consumer needed a component for use during runtime, compatible with Java 8
- Other compatible attribute:
- Doesn't say anything about org.gradle.plugin.api-version (required '8.0')
- Variant 'javadocElements' capability com.android.tools.build:gradle:8.0.2 declares a component for use during runtime, and its dependencies declared externally:
- Incompatible because this component declares documentation and the consumer needed a library
- Other compatible attributes:
- Doesn't say anything about its target Java version (required compatibility with Java 8)
- Doesn't say anything about its elements (required them packaged as a jar)
- Doesn't say anything about org.gradle.plugin.api-version (required '8.0')
- Variant 'runtimeElements' capability com.android.tools.build:gradle:8.0.2 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
- Incompatible because this component declares a component, compatible with Java 11 and the consumer needed a component, compatible with Java 8
- Other compatible attribute:
- Doesn't say anything about org.gradle.plugin.api-version (required '8.0')
- Variant 'sourcesElements' capability com.android.tools.build:gradle:8.0.2 declares a component for use during runtime, and its dependencies declared externally:
- Incompatible because this component declares documentation and the consumer needed a library
- Other compatible attributes:
- Doesn't say anything about its target Java version (required compatibility with Java 8)
- Doesn't say anything about its elements (required them packaged as a jar)
- Doesn't say anything about org.gradle.plugin.api-version (required '8.0')
The consumer was configured to find a library for use during runtime, compatible with Java 8, packaged as a jar
とあるので、Android Studio
> Settings...
> Build, Execution, Deployment
> Gradle
の中のGradle JDKを確認したところ、確かにJava 8が指定されていた。
Incompatible because this component declares a component for use during compile-time, compatible with Java 11 and the consumer needed a component for use during runtime, compatible with Java 8
ともあるので、一旦これをJava 11に上げることでSync Project with Gradle Files
は成功。
次にRun 'androidApp'
を実行したところ、次のエラー。
Build file '/Users/XXXX/AndroidStudioProjects/KmmSandbox/androidApp/build.gradle.kts' line: 1
An exception occurred applying plugin request [id: 'com.android.application']
> Failed to apply plugin 'com.android.internal.application'.
> Android Gradle plugin requires Java 17 to run. You are currently using Java 11.
...
Gradle JDKをJava 17に上げることでRun 'androidApp'
は成功。
スタートラインに立つまでが一番大変😇
Android/iOSともに初期画面は表示できた。
Android | iOS |
---|---|
Android StudioからiOSのシミュレータを起動できるのは地味に便利だな〜
ちなみに、起動するシミュレータのバージョンはRun/Debug Configurations
のExecution Terget
から変更できる模様。
公式サイトのExamine the project structureを見ると、各モジュールの説明が書いてある。
-
shared
module-
shared
is a Kotlin module that contains the logic common for both Android and iOS applications – the code you share between platforms. - It uses Gradle as the build system that helps you automate your build process. The shared module builds into an Android library and an iOS framework.
-
-
androidApp
module-
androidApp
is a Kotlin module that builds into an Android application. - It uses Gradle as the build system.
- The androidApp module depends on and uses the shared module as a regular Android library.
-
-
iosApp
module-
iosApp
is an Xcode project that builds into an iOS application. - It depends on and uses the shared module as an iOS framework.
- The shared module can be used as a regular framework or as a CocoaPods dependency, based on what you've chosen in the previous step in iOS framework distribution.
- In this tutorial, it's a regular framework dependency.
-
iosApp
モジュールがshared
モジュールを参照するためにデフォルトで行っている設定があまりよく分かっていないが、この部分でshared
という名前のiOS frameworkを作成している模様。
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
}
}
iosApp.xcodeproj
をXcodeで開き、TARGETS
> Build Settings
の中のLinkingを確認したところ、Other Linker Flagsに-framework shared
とあるので、どうやらこの部分の設定によってiosApp
モジュールがshared
モジュール(実体はiOS framework)を参照できるようになっている模様。
ここに全部書いてあったw
サポートされているTargetは公式サイトのKotlin/Native target supportに記載されている。
-
iosX64()
- Apple iOS simulator on x86-64 platforms
-
iosArm64()
- Apple iOS and iPadOS on ARM64 platforms
-
iosSimulatorArm64()
- Apple iOS simulator on Apple Silicon platforms
公式サイトのConnect to platform-specific APIsにある通り、プラットフォーム固有のAPIに依存したロジックを共有したいときは、expected and actual declarations
というメカニズムが使用できる。
-
shared
モジュール内で、共有したい関数やクラスをexpect
キーワードを用いて宣言 (expected declaration) -
androidApp
モジュールおよびiosApp
モジュール内で、1.で作成した関数やクラスの実装をactual
キーワードを用いて宣言 (actual declaration)
例:
// In `shared` module
expect fun randomUUID(): String
// In `androidApp` module
actual fun randomUUID(): String = UUID.randomUUID().toString()
// In `iosApp` module
actual fun randomUUID(): String = NSUUID().UUIDString()
公式サイトのAdd dependencies to your projectにあるdaysUntilNewYear()
を追加した(commit)。
モックライブラリの話もあり、このままだとテストしづらいので、あとで少しリファクタリングする。
また、以下の観点でユニットテストを書く予定。
- 年末
- 年始
- 閏年
公式サイトのAdd more dependenciesの通りに依存関係を追加したところ、以下のエラーが発生。
Cannot add a KotlinSourceSet with name 'iosMain' as a KotlinSourceSet with that name already exists.
すでに存在するiosMain
を作成しようとしてエラーになっていたので、以下のようにby getting
を使うように修正した(commit)。
- val iosMain by creating {
+ val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
}
}
これらのSourceSetは、公式サイトのNew approach to source set hierarchyにある機能をtargetHierarchy.default()
の呼び出しにより有効化することによって追加されている模様。
なお、この部分の設定により、今回のプロジェクトではiosX64
がTargetとしてさらに追加されている。
実装を進めていったところ、以下のコンパイルエラーが発生した。
Compilation failed: Internal compiler error: no implementation found for FUN DEFAULT_PROPERTY_ACCESSOR name:<get-parent> visibility:public modality:ABSTRACT <> ($this:kotlinx.coroutines.Job) returnType:kotlinx.coroutines.Job?
when building itable for CLASS INTERFACE name:Job modality:ABSTRACT visibility:public superTypes:[kotlin.coroutines.CoroutineContext.Element]
implementation in CLASS CLASS name:ChannelJob modality:FINAL visibility:private superTypes:[io.ktor.utils.io.ReaderJob; io.ktor.utils.io.WriterJob; kotlinx.coroutines.Job]
at /opt/buildAgent/work/8d547b974a7be21f/ktor-io/common/src/io/ktor/utils/io/Coroutines.kt (156:1)
CLASS CLASS name:ChannelJob modality:FINAL visibility:private superTypes:[io.ktor.utils.io.ReaderJob; io.ktor.utils.io.WriterJob; kotlinx.coroutines.Job]
* Source files:
* Compiler version info: Konan: 1.8.21 / Kotlin: 1.8.21
* Output kind: FRAMEWORK
org.jetbrains.kotlinx:kotlinx-coroutines-core
のバージョンを、公式サイト記載の1.6.4
でなくAndroid Studioで推奨された最新版の1.7.1
にしたことが関連しているようなので、原因を調べる。
ちょうど寝てる間に解決された気配があるw
上記Issueの修正が適用されたバージョンが使えるようになるまでは、org.jetbrains.kotlinx:kotlinx-coroutines-core
のバージョンを1.6.4
に下げておこう。
Xcodeからビルドしようとすると、以下のエラーが発生する。
Command PhaseScriptExecution failed with a nonzero exit code
詳細を見ると、
Build file '/Users/XXXX/AndroidStudioProjects/KmmSandbox/androidApp/build.gradle.kts' line: 1
An exception occurred applying plugin request [id: 'com.android.application']
> Failed to apply plugin 'com.android.internal.application'.
> Android Gradle plugin requires Java 17 to run. You are currently using Java 11.
...
と出力されており、これはTARGETS
> Build Phrases
の中のRun Buildに記載されている
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode
の実行時に発生している模様。
cd "$SRCROOT/.."
java -version
./gradlew :shared:embedAndSignAppleFrameworkForXcode
のように変更してJavaのバージョンを確認したところ、確かにopenjdk version "11.0.19" 2023-04-18 LTS
が先頭に出力される…🤔