1. 序章

このチュートリアルでは、Kotlinでのマルチプラットフォームプログラミングについて理解します。 JVM、JS、ネイティブなどの複数のプラットフォームを対象とする単純なアプリケーションを開発します。

これは、マルチプラットフォームプログラミングの利点と、それらを効果的に適用できるさまざまなユースケースを理解するのにも役立ちます。

2. マルチプラットフォームプログラミングとは何ですか?

多くの場合、実行するプラットフォームに依存しないプログラムの部分を作成します。 たとえば、REST APIを呼び出してデータをフェッチし、結果を返す前に追加の処理を適用します。 バックエンドをターゲットにする場合はJavaで、Webをターゲットにする場合はJSで、モバイルプラットフォームをターゲットにする場合はAndroidまたはiOSで、このコードをほぼ複製する必要があります。

コードを1回記述して、複数のプラットフォームをターゲットにできるといいのではないでしょうか。 さて、これはマルチプラットフォームプログラミングの基本的な約束です! Kotlinマルチプラットフォームプログラミングは、この約束を果たすのに適しています。 このチュートリアルでは、その方法を説明します。

JavaやCなどの高級言語でプログラムを作成する場合、WindowsやLinuxなどのプラットフォームで実行するようにプログラムをコンパイルする必要があります。 ただし、Javaでは、バイトコードと呼ばれる中間形式にコンパイルされます。 これにより、Javaに「一度書けばどこでも実行できる」という有名なタグラインが与えられます。 ただし、このバイトコードを実行するには、ネイティブプラットフォームに固有のJVMが必要です。

Kotlinマルチプラットフォームはこの概念を一段高くし、 JVM、JS、さらにはネイティブプラットフォームなどの複数のプラットフォームで同じコードを直接実行することを約束します。 ターゲットプラットフォームで実行されている仮想マシンに依存しません。 これにより、マルチプラットフォームプログラミングがKotlin言語の重要な利点の1つになります。

また、これにより、異なるプラットフォームで同じコードを記述して維持するために必要な労力が大幅に削減されます。

3. Kotlinはそれをどのようにサポートしていますか?

マルチプラットフォームプログラミングの魔法を理解する前に、Kotlinが実際にそれをどのようにサポートしているかを理解するために時間を費やす必要があります。 このセクションでは、マルチプラットフォームプログラミングを有効にして使いやすくするためにKotlinが提供するツールとテクニックのいくつかについて説明します。

3.1. 基本をカバーする

Javaコードを取得して任意のJVMで実行できる場合、その背後にある力は、コードをバイトコードに変換するJavaコンパイラーにあります。 これにより、異なる言語が同じJVMをターゲットにすることができます。 たとえば、同じJVMプラットフォームでKotlin、Groovy、またはScalaコードを実行できます。 それぞれに、互換性のあるバイトコードを生成できるコンパイラが付属しています。

したがって、同じコードを取り、異なるプラットフォームが理解できるフォーマットを生成できるコンパイラを思い付くことができない理由はありません。 もちろん、これは口で言うほど簡単ではなく、場合によっては不可能かもしれません。 場合によっては、これはコンパイラではなく、トランスパイラーであり、ソースからソースへのトランスレータにすぎません。

それにもかかわらず、これがKotlinがマルチプラットフォームプログラミングを可能にする方法です。 さまざまなプラットフォームで共通のコードを機能させるために、Kotlin は、Kotlin / JVM、Kotlin / JS、Kotlin /Nativeなどのプラットフォーム固有のコンパイラとライブラリを提供します。

ここでは、一般的なKotlinでアプリケーションの再利用可能な部分を作成し、マルチプラットフォームプログラミングのサポートにより、すべてのターゲットプラットフォームで動作します。 たとえば、REST APIを呼び出して一部のデータをフェッチすることは、共通の一部になるのに適した候補になる可能性があります。

3.2. プラットフォーム間でのソースコードの再利用

Kotlinマルチプラットフォームは、依存関係を明示的にし、ソースセット間でコードを再利用する階層にソースコードを編成します。 すべてのプラットフォーム固有のソースセットは、デフォルトで共通のソースセットに依存します

一般的なコードは、KotlinがHTTP呼び出しの作成、データのシリアル化の実行、同時実行の管理などの一般的なタスクに提供する多くのライブラリに依存する可能性があります。 さらに、Kotlinのプラットフォーム固有のバージョンは、ターゲットプラットフォームのプラットフォーム固有の機能を活用するために使用できるライブラリを提供します。

したがって、再利用可能なビジネスロジックを共通に保ち、ネイティブ機能を活用するユーザーインターフェイスなどのアプリケーションの一部を開発することにしました。 さらに、Kotlinマルチプラットフォームプログラミングを使用すると、すべてのプラットフォーム間でコードを共有したり、より選択的に共有したりできます。

たとえば、上の画像では、すべてのプラットフォームで共通のコードが共有されていますが、 Linux、Windows、macOSなどのネイティブプラットフォームでのみ共有される共通のネイティブコードもあります。

3.3. プラットフォーム固有のAPIの開発

これまで、Kotlinマルチプラットフォームプログラミングにより、プラットフォーム固有のソースセット間で共通のコードを再利用する方法を見てきました。 ただし、場合によっては、共通のプラットフォーム固有のAPIを定義してアクセスすることが望ましい場合があります。 これは、特定の一般的で再利用可能なタスクが専門化されており、プラットフォーム固有の機能を活用するためにより効率的である領域で特に役立ちます。

Kotlinマルチプラットフォームは、この目的を達成するために、期待される宣言と実際の宣言のメカニズムを提供します。 たとえば、共通ソースセットは関数を expected として宣言でき、プラットフォーム固有のソースセットは、actual宣言で対応する関数を提供する必要があります。

ここでは、ご覧のとおり、共通ソースセットでexpectedとして宣言された関数を使用しています。 The 一般的なコードは、それがどのように実装されているかを気にしません。 これまでのところ、ターゲットはプラットフォーム固有の実装を提供しますこの関数の。

これらの宣言は、関数、クラス、インターフェイス、列挙、プロパティ、およびアノテーションに使用できます。

3.4. ツールからのサポート

KotlinはJetBrainsの出身であり、 IntelliJ IDEA のようなユーザーフレンドリーなIDEの開発のパイオニアでもあるため、マルチプラットフォームプログラミングの統合サポートを期待するのは当然です。 実際、IntelliJ IDEAは、Kotlinでマルチプラットフォームプロジェクトを作成するためのいくつかのプロジェクトテンプレートを提供しています。 これにより、マルチプラットフォームプロジェクトを作成するプロセス全体が非常にシームレスかつ迅速になります。

プロジェクトテンプレートを使用してマルチプラットフォームプロジェクトを作成すると、kotlin-multiplatformGradleプラグインが自動的に適用されます。

plugins {
    kotlin("multiplatform") version "1.4.0"
}

このkotlin-multiplatformプラグインは、複数のプラットフォームで動作するアプリケーションまたはライブラリを作成するためのプロジェクトを構成します。 構成は、通常どおり、選択したDSLに応じて、ファイルbuild.gradleまたはbuild.gradle.ktに入れられます。

kotlin {
    jvm {
        withJava()
    }
    js {
        browser {
            binaries.executable()
        }
    }
    sourceSets {
        val commonMain by getting {
            dependencies {
                .....
            }
        }
        val commonTest by getting {
            dependencies {
                .....
            }
        }
        val jvmMain by getting {
            dependencies {
                .....
            }
        }
        val jsMain by getting {
            dependencies {
                .....
            }
        }
    }
}

構成でわかるように、上部に「kotlin」拡張機能があり、ターゲット、ソースセット、および依存関係の構成が含まれています。

さらに、各ターゲットは1つ以上のコンパイルを持つことができます。 Kotlinマルチプラットフォームプロジェクトは、アーティファクトを生成するためにコンパイルを使用します。 ターゲットごとに、デフォルトのコンパイルには、JVM、JS、およびネイティブターゲットの「メイン」および「テスト」コンパイルが含まれます。

4. マルチプラットフォームプログラミングの実践

このセクションでは、これまでに学んだ理論のいくつかを実践します。 JVM、JS、ネイティブなどの複数のターゲットプラットフォームで再利用する共有コードを使用して、簡単な計算機アプリケーションを開発します。

4.1. マルチプラットフォームプロジェクトの作成

IDEAプロジェクトテンプレートの1つを使用して、Kotlinでマルチプラットフォームライブラリのスケルトンを生成します。 IntelliJ IDEACommunityEditionのプロジェクトテンプレート選択ウィザードを見てみましょう。

アプリケーション、モバイルライブラリ、モバイルアプリケーション、ネイティブアプリケーション、さらにはフルスタックアプリケーションなど、他の種類のマルチプラットフォームプロジェクトのスケルトンも同じように簡単に作成できることに注意してください。 上記のウィザードを使用してKotlinマルチプラットフォームライブラリプロジェクトを作成すると、デフォルトの構成設定とコードベースの構造が提供されます。

上記のように、ウィザードはデフォルトで、JVM、JS、およびネイティブの共通コードとターゲットの構成と構造を生成します。 もちろん、ターゲットを手動で削除したり、サポートされているプラットフォームのターゲットプリセットを使用してターゲットを追加したりすることもできます。

さらに、必要に応じてデフォルトのターゲットGradle構成を変更できます。 後で、JavaScriptモジュールのフロントエンドアプリケーションとネイティブモジュールのコマンドラインアプリケーションをサポートするようにこれらの構成を変更する方法を説明します。

4.2. 共通コードの記述

プロジェクト構造を設定したら、後でターゲットプラットフォームで使用する一般的なコードを記述します。 共通コードは、前に作成したプロジェクト構造のcommonMainディレクトリとcommonTestディレクトリにあります。

commonMainで電卓をシミュレートする簡単なルーチンを作成します。

fun add(num1: Double, num2: Double): Double {
    val sum = num1 + num2
    writeLogMessage("The sum of $num1 & $num2 is $sum", LogLevel.DEBUG)
    return sum
}

fun subtract(num1: Double, num2: Double): Double {
    val diff = num1 - num2
    writeLogMessage("The difference of $num1 & $num2 is $diff", LogLevel.DEBUG)
    return diff
}

fun multiply(num1: Double, num2: Double): Double {
    val product = num1 * num2
    writeLogMessage("The product of $num1 & $num2 is $product", LogLevel.DEBUG)
    return product
}

fun divide(num1: Double, num2: Double): Double {
    val division = num1 / num2
    writeLogMessage("The division of $num1 & $num2 is $division", LogLevel.DEBUG)
    return division
}

ご覧のとおり、これらは単純なKotlin関数ですが、複数のプラットフォームで再利用できます。 したがって、それらを1か所で宣言して維持することで大きなメリットが得られます。

ここで唯一興味深いのは、まだ定義していない関数writeLogMessageです。 それを定義する方法を見てみましょう:

enum class LogLevel {
    DEBUG, WARN, ERROR
}

internal expect fun writeLogMessage(message: String, logLevel: LogLevel)

したがって、ここでは、キーワードexpectedを使用して関数writeLogMessageを宣言しました。 前に説明したように、これにより、マルチプラットフォームプロジェクトは、キーワードactualで宣言された関数のプラットフォーム固有の実装を探す必要があります。 これらの宣言は、同じ名前を持ち、同じパッケージに含まれている必要があります。

では、なぜメソッド writeLogMessage を使用してこれを行うのですか? 基本的にの理論的根拠は、プラットフォーム固有の依存関係を持つルーチンが存在する可能性があるということです。 たとえば、いくつかのプラットフォーム固有の機能を使用して、ログの書き込みをより効率的に行うことができます。

ここでの例はデモンストレーションのみを目的としており、expectedおよびactual宣言を使用するための有効なケースを必ずしも示しているわけではありません。 それでも、非常に注意して、expect宣言と実際の宣言を控えめに使用する必要があります。 私たちの努力は、共通モジュール自体に可能な限り多くの機能を実装することです。

4.3. 一般的なコードのテストを書く

簡単な電卓関数のテストをいくつか書いてみましょう。

@Test
fun testAdd() {
    assertEquals(4.0, add(2.0, 2.0))
}

@Test
fun testSubtract() {
    assertEquals(0.0, subtract(2.0, 2.0))
}

@Test
fun testMultiply() {
    assertEquals(4.0, multiply(2.0, 2.0))
}

@Test
fun testDivide() {
    assertEquals(1.0, divide(2.0, 2.0))
}

ここには特別なことは何もありません。これらは純粋で単純な単体テストであり、正常に機能します。 ただし、興味深いことに、それらを実行する場合、IntelliJIDEAにターゲットを選択するように求める新しいウィンドウが表示されます。

これは、でテストを実行するターゲットを定義する必要があるため理解できます。 複数のターゲットを選択して、それらすべてで一度にテストを実行できます。

5. Kotlin/JVMを使用したJVMのターゲット

Kotlinが開始されたとき、それは主にターゲットプラットフォームとしてのJava仮想マシン(JVM)用に設計されていました。 当時のJavaバージョンに典型的ないくつかの課題に対処しようとしました。 ただし、プログラミング言語としてのKotlinはJVMにバインドされることはなく、常に複数のプラットフォームで実行することを意図していました。

では、ここでかなり長い間Javaを使用しているのに、なぜサーバーサイドプログラミングをKotlinに切り替えるのでしょうか。 最近のバージョンのJavaはギャップを埋めようとしていますが、それでもKotlinからすぐに得られるメリットの一部を提供することはできません。 Kotlinはコルーチンを使用した構造化同時実行などのいくつかの優れた機能を備えた簡潔で表現力豊かなコードを記述するのに最適です。

さらに、Kotlinは、Javaプラットフォームに固有の関数とアノテーション、およびJavaとの優れた相互運用性を提供します。 したがって、必要なだけ使用することも、使用することもできます。 このセクションでは、KotlinがKotlin/JVMを使用してJVMをターゲットにする方法を説明します。

5.1. Kotlin/JVMコンパイラ

Kotlinには、KotlinソースファイルをJavaクラスファイルにコンパイルするコンパイラが付属しています。Javaクラスファイルは、任意のJVMで実行できます。 通常、マルチプラットフォームライブラリのようなKotlinプロジェクトをビルドすると、このコンパイラが自動的にこのコンパイラを使用してクラスファイルを生成します。 ただし、kotlinckotlinc-jvmなどのコマンドラインツールを使用して、Kotlinソースファイルをクラスファイルにコンパイルすることもできます。

JavaコードをKotlinと混合することは可能ですが、絶対に必要な場合にのみ混合する必要があります。 マルチプラットフォームプロジェクトのJVMモジュールでは、いくつかのJavaライブラリを利用してKotlinソースを記述したり、より理にかなっている場合はJavaソースを記述したりすることができます

ただし、KotlinコンパイラはKotlinソースファイルのみをコンパイルし、必要に応じてソースディレクトリからJava参照をロードします。 したがって、次のようなJavaソースファイルをコンパイルするためのJavaコンパイラも必要です。

マルチプラットフォームプロジェクトでJavaとKotlinの両方のソースファイルを使用できるようにするには、Gradle構成に必要な変更を加える必要があります。

jvm {
    withJava()
}

特定のバージョンのJVMのクラスファイルを生成することもできます。 デフォルトでは、Javaバージョン1.6を対象としています。 Gradle構成で構成することにより、1.8のようなJavaバージョンをターゲットにできます。

jvm {
    compilations.all {
        kotlinOptions.jvmTarget = "1.8"
    }
}

5.2. Kotlin/JVMでのコードの開発と再利用

プロジェクトのjvmMainおよびjvmTestディレクトリにJVMプラットフォームに固有のコードを追加します。 JVMターゲットで最初に提供する必要があるのは、メソッドwriteLogMessageの実装です。

internal actual fun writeLogMessage(message: String, logLevel: LogLevel) {
    println("Running in JVM: [$logLevel]: $message")
}

ここでは、JVMプラットフォームの利点を引き出す特別なことは何もありません。 ただし、この関数を宣言actualでマークしていることに注意してください。

この単純なアプリケーションのJavaソースを作成して、Kotlinと共存する方法を示します。 一般的な単純な演算を活用して、さらにいくつかの数学演算を提供する単純なルーチンを記述します。

public static Double square(Double number) {
    return CalculatorKt.multiply(number, number);
}

これは、共通モジュールの関数を使用して機能する単純なJavaメソッドです。 Javaの静的メソッドとしてKotlinの関数addにアクセスする方法に注意してください。 関数addをラップするクラスを作成したことがないことに注意してください。 ただし、 Kotlin / JVMコンパイラは、ファイルの名前でクラスを生成し、静的メソッドとして関数を追加します

6. Kotlin/JSを使用したJavaScriptのターゲティング

次に、 Kotlin /JSを使用してKotlinマルチプラットフォームプロジェクトからJavaScriptプラットフォームをターゲットにする方法を説明します。 さらに掘り下げる前に、ターゲットとしてのJavaScriptの利点を理解するために時間を費やしましょう。 JavaScriptはフロントエンドアプリケーションの作成にかなり人気があります。 その理由の一部は、JavaScriptがかなり早い段階から最も人気のあるWebブラウザでサポートされてきたことです。

Angular React Vue などのより洗練されたライブラリとフレームワークの出現により、フロントエンドアプリケーションの開発がよりシンプルで直感的になりました。 さらに、 Node.js の追加により、JavaScriptは、少なくとも一部のユースケースでは、サーバー側の実装でも人気のある選択肢になりました。 明らかに、ターゲットとしてのJavaScriptは、Kotlin開発者にとって非常に理にかなっています。

JetBrainsは、React、Mocha、styled-componentsなどの人気のあるJavaScriptライブラリ用にいくつかのKotlinラッパーを維持しています。 これらは、Kotlin開発者がKotlinでタイプセーフなフロントエンドアプリケーションを作成するための便利な抽象化を提供します。 さらに、Gradleプラグインは、 webpack を使用したアプリケーションの制御とバンドル、 yarn を使用したnpmからのJavaScript依存関係の追加など、多くの重要な機能を提供します。

6.1. Kotlin/JSコンパイラ

Kotlinリリースに付属するKotlin/JSコンパイラは、KotlinソースをJavaScriptソースに変換します。 結果のJavaScriptソースは、WebブラウザーやNode.jsに付属しているJavaScriptエンジンなどの任意のJavaScriptエンジンで実行できます。 したがって、Kotlin/JSコンパイラをトランスパイラーと呼ぶこともできます。

現在のKotlin/JSコンパイラはECMAScript5 (ES5)を対象としており、JavaScriptの標準として機能します。

Kotlin の最近のリリースには、中間表現(IR)に基づいた、Kotlin/JSおよびKotlin/JVM用の代替コンパイラバックエンドが付属しています。 これは、Kotlin1.4.0以降のアルファリリースとしてのみ利用可能です。 これは、Kotlin / JVM、Kotlin / JS、およびKotlin/NativeにIRに基づく統合バックエンドを提供するためのものです。

基本的に、JavaScriptコードをKotlinソースから直接生成する代わりに、IRコンパイラーは最初にKotlinソースを中間表現(IR)に変換します。 次に、IRコンパイラは、これらの中間表現をJavaScriptソースなどのターゲット表現にさらにコンパイルします。

これにより、IRコンパイラは、デフォルトのコンパイラでは困難だった積極的な最適化やその他のことを実行できます。 たとえば、Kotlin / JSの場合、デッドコードを排除することでより軽量な実行可能ファイルを生成し、相互運用性を高めるためにTypeScriptファイルを生成できます。

Gradle構成を簡単に変更することで、デフォルトのコンパイラからIRコンパイラに切り替えることができます。

kotlin {
    js(IR) {
    }
}

これまで見てきたように、 Kotlin/JSプロジェクトは2つの異なる実行環境をターゲットにすることができます。 これには、ブラウザーでのクライアント側スクリプト用のWebブラウザーと、ブラウザー外でのサーバー側スクリプト用のNode.jsが含まれます。

Kotlin / JSの実行環境の選択も、Gradle構成の簡単な変更です。

kotlin {
    js {
        browser {
        }   
    }
}

ありがたいことに、Gradleプラグインは、選択した環境で作業するためのタスクを自動的に構成し、追加の構成なしでKotlin / JSプロジェクトをビルド、実行、およびテストできるようにします。

6.2. Kotlin/JSでのコードの開発と再利用

簡単なアプリケーションでは、Reactを使用して計算機の基本的なフロントエンドを開発します。 JavaScriptターゲットのコードは、ディレクトリjsMainおよびjsTestにあります。

前と同じように、最初に追加する必要があるのは、関数 writeLogMessage の実装であり、宣言actualでマークします。

internal actual fun writeLogMessage(message: String, logLevel: LogLevel) {
    when (logLevel) {
        LogLevel.DEBUG -> console.log("Running in JS: $message")
        LogLevel.WARN -> console.warn("Running in JS: $message")
        LogLevel.ERROR -> console.error("Running in JS: $message")
    }
}

次に、Gradle構成に必要なフロントエンドの依存関係を追加する必要があります。

val jsMain by getting {
    dependencies {
        implementation("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2")
        implementation("org.jetbrains:kotlin-react:16.13.1-pre.110-kotlin-1.4.10")
        implementation("org.jetbrains:kotlin-react-dom:16.13.1-pre.110-kotlin-1.4.10")
        implementation("org.jetbrains:kotlin-styled:1.0.0-pre.110-kotlin-1.4.10")
    }
}

これらは基本的に、KotlinでReactコードを記述できるようにするKotlinラッパーです。

Reactで最初に必要なのは、Reactがターゲットにできるルート要素を固定するための単純なHTMLファイルです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS Client</title>
</head>
<body>
<script src="kotlin-multiplatform.js"></script>
<div id="root"></div>
</body>
</html>

次に、このルート要素でReactアプリケーションをレンダリングするための簡単な関数を記述します。

fun main() {
    window.onload = {
        render(document.getElementById("root")) {
            child(Calculator::class) {
                attrs {
                    value = "0"
                }
            }
        }
    }
}

コンポーネントにプロパティを渡し、状態を維持することを期待しているため、Kotlinでプロパティを定義する必要があります。

external interface CalculatorProps : RProps {
    var value: String
}

data class CalculatorState(val value: String) : RState

最後に、ロードするコンポーネントを定義するCalculatorクラスを作成する必要があります。

@JsExport
class Calculator(props: CalculatorProps) : RComponent<CalculatorProps, CalculatorState>(props) {

    init {
        state = CalculatorState(props.value)
    }

    override fun RBuilder.render() {
        styledLabel {
            css {
            }
            + "Enter a Number: "
        }
        styledInput {
            css {
            }
            attrs {
                type = InputType.number
                value = state.value
                onChangeFunction = { event ->
                    setState(
                        CalculatorState(value = (event.target as HTMLInputElement).value)
                    )
                }
            }
        }
        styledDiv {
            css {
            }
            +"Square of the Input: ${
                multiply(state.value.toDouble(), state.value.toDouble())}"
        }
    }
}

これは、入力を定義し、入力した値でローカル状態を更新する単純なReactコンポーネントです。 さらに、共通モジュールにある関数 product を使用して、正方形を計算し、コンポーネントに戻します。

Gradleプラグインは、生成されたJavaScriptアーティファクトを提供するためにwebpack-dev-serverのサポートが付属しています。 次のタスクを実行して、Kotlin/JSプロジェクトを実行できます。

gradlew jsRun

次に、任意のブラウザを使用して単純なアプリケーションにアクセスできます。

意図的にスタイリングを省略しましたが、CSSを完全にサポートしており、好きなだけ派手なインターフェイスを作成できます。 Gradle構成を簡単に変更して、webpackのCSSとスタイルローダーを有効にします。

browser {
    commonWebpackConfig {
        cssSupport.enabled = true
    }
}

7. Kotlin/Nativeでネイティブをターゲットにする

ネイティブプラットフォームは、おそらくKotlinをサポートするために最も多様で複雑です。 Kotlin / Nativeは、サポートされているターゲットプラットフォームに固有のネイティブバイナリにKotlinソースを直接コンパイルしようとします。 しかし、これらの長さに行くことの利点は何ですか?

Linux、Windows、またはmacOSで実行する予定のデスクトップアプリケーションを開発していると想像してください。 もちろん、1つの方法は、プラットフォームごとに個別に開発することですが、ここでの労力の無駄を理解することは難しくありません。 JVMのような仮想マシンで実行するように開発することもできますが、そのようなマシンをすべてのネイティブプラットフォームで使用できるようにする必要があります。

アプリケーションを1回作成し、プラットフォーム固有のバイナリを生成して、依存関係なしでどこでも実行できることができたら素晴らしいと思いませんか? これは、 Kotlin /Nativeがいくつかのネイティブプラットフォームに提供することを約束しているものです。

さらに、複数のプラットフォーム向けのモバイルアプリケーションの開発など、他の多くのユースケースにも適用できます。 AndroidおよびiOSで実行される単一のモバイルアプリケーションを使用すると、複数のプラットフォーム固有のライブラリを学習して長期にわたって維持するための労力を大幅に節約できます。

7.1. Kotlin/ネイティブコンパイラ

Kotlin / Nativeは、Kotlin/NativeコンパイラおよびKotlin標準ライブラリのネイティブ実装用のLLVMベースのバックエンドを提供します。 Kotlin / Nativeコンパイラ自体は、Konanとして知られています。 LLVMは基本的に、任意のプログラミング言語のフロントエンドと任意の命令セットアーキテクチャのバックエンドを開発するために使用できるコンパイラインフラストラクチャです。

言語に依存しない中間表現として機能する、さまざまな変換用に最適化されたポータブルで高レベルのアセンブリ言語を提供します。 元々はCおよびC++用に実装されていましたが、現在、Kotlinを含むLLVMをサポートするコンパイラーを備えたいくつかの言語があります。

Kotlin / Nativeは、Gradle構成で便利に選択できる多数のプラットフォームをサポートしています。

  • Linux(x86_64、arm32、arm64、MIPS、MIPSリトルエンディアン)
  • Windows(mingw x86_64、x86)
  • Android(arm32、arm64、x86、x86_64)
  • iOS(arm32、arm64、シミュレーターx86_64)
  • macOS(x86_64)
  • tvOS(arm64、x86_64)
  • watchOS(arm32、arm64、x86)
  • WebAssembly(wasm32)

ここで、Gradle構成で、ホストオペレーティングシステムがサポートされているかどうかを確認するためのチェックがあることに注意してください。

kotlin {
    val hostOs = System.getProperty("os.name")
    val isMingwX64 = hostOs.startsWith("Windows")
    val nativeTarget = when {
        hostOs == "Mac OS X" -> macosX64("native")
        hostOs == "Linux" -> linuxX64("native")
        isMingwX64 -> mingwX64("native")
        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
    }
}

IntelliJ IDEAプロジェクトテンプレートを使用してマルチプラットフォームプロジェクトを開発する場合、これはデフォルトで処理されます。

7.2. Kotlin/Nativeでのコードの開発と再利用

前に見たように、マルチプラットフォームモバイルアプリケーションの開発は、おそらくKotlin/Nativeの主なユースケースの1つです。 ただし、AndroidまたはiOS用の実際のアプリケーションの開発とテストのニュアンスをカバーするために、より詳細に説明します。 したがって、アプリケーションをWindowsターゲットプラットフォーム用の単純なコマンドラインアプリケーションに制限します。

以前、共通モジュールの簡単な操作を利用して、数値の2乗を計算するためのユーザーインターフェイスを開発しました。 それを拡張し、同じことを実行するコマンドラインアプリケーションを作成するのは論理的です。

規則に従って、ネイティブターゲットのコードはディレクトリnativeMainおよびnativeTestにあります。 まず、ネイティブプラットフォーム用の関数 writeLogMessage の実装を追加して、宣言actualでマークを付けましょう。

internal actual fun writeLogMessage(message: String, logLevel: LogLevel) {
    println("Running in Native: [$logLevel]: $message")
}

次に、Gradle構成でアプリケーションのエントリポイントを定義する必要があります。

nativeTarget.apply {
    binaries {
        executable {
            entryPoint = "com.baeldung.kotlin.multiplatform.main"
        }
    }
}

最後に、 main 関数を定義する必要があります。これにより、コマンドラインアプリケーションが実際に駆動されます。

fun main() {
    println("Enter a Number:")
    val number = readLine()!!.toInt()
    println("Square of the Input: ${multiply(number, number)}")
}

共通モジュールで前に定義した関数productを利用しているため、ここでは特別なことは何も起こりません。

Windowsのコマンドプロンプトから生成された実行可能ファイルを実行することで、これが実際に動作していることを確認できます。

同様に、 LinuxやmacOSなどの別のプラットフォームでこのアプリケーションをビルドすると、他の依存関係なしでネイティブに実行できるプラットフォームの実行可能ファイルが生成されます。

8. 結論

このチュートリアルでは、マルチプラットフォームプログラミングの基本と、Kotlinがそれをどのようにサポートしているかについて学びました。 IntelliJIDEAプロジェクトテンプレートを使用してシンプルなマルチプラットフォームプロジェクトを開発しました。 これにより、Kotlinで共通モジュールを作成できました。

さらに、Reactベースのユーザーインターフェイスを開発するためにKotlin / JSで、Windows用のコマンドラインアプリケーションを開発するためにKotlin / Nativeで、この共通モジュールのコードを再利用しました。

いつものように、例のコードはGitHubから入手できます。