Kotlin/Nativeを触ってみた

#はじめに
Kotlin/Nativeのベータ版が出たということで、
multiplatform開発の環境構築をしてみました。
とりあえずAndroidとiOSそれぞれのエミュレータ（シミュレータ）で動くとこまでとなってます。
基本的に下記公式サイトのチュートリアルに沿ってやっていきます。
https://kotlinlang.org/docs/tutorials/native/mpp-ios-android.html

#実践！
###事前準備
下記をインストール

- AndroidStudio 3.2 (IntelliJ IDEAでも可)
- Xcode 10

※macOS 10.13.6上でやってます。

###Android編
#####Kotlin ver1.3以上をインストール
とはいえ現状まだ1.3正式版はリリースされていませんのでRC版を入れます。
このときのRC版最新バージョンは1.3.0-rc-146となってます。
AndroidStudio上で
Preferences → Languages & Frameworks → Kotlin Updates
のUpdate channelをEarly Access Preview 1.3を選択してインストールし、AndroidStudioを再起動します。
<img width="600" alt="Preferences" src="https://qiita-image-store.s3.amazonaws.com/0/257063/df567edb-b65a-7447-9ed4-094abecc2a9e.png">

#####プロジェクト作成
通常通りAndroidプロジェクトを作成します。
Include Kotlin supportはチェックをいれます。
ターゲットバージョンなどは適当に。
最小構成で作るのでEmpty Activityを選択して最後にFinish。


#####build.gradleの編集
プロジェクトルートにあるbuild.gradleに`maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }`をrepositories **2箇所** に追記します。
ついでに、`ext.kotlin_version`が先程インストールしたversionになっているか確認しましょう。

```build.gradle
buildscript {
    ext.kotlin_version = '1.3.0-rc-146'
    repositories {
        google()
        jcenter()
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
```


#####gradle-wrapper.propertiesの編集
gradle/wrapper/gradle-wrapper.propertiesの`distrubutionUrl`を`4.10.2`に変更します。

```gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
```
ここまでやったら一旦`Sync Now`しましょう

#####Shared Moduleの作成
Android・iOS両方から呼び出せるmoduleを作成していきます。
まずは`SharedCode/src/commonMain/kotlin/common.kt`を作成します。
中にはPlatform名を返すコードを書きます。

```common.kt
package org.kotlin.mpp.mobile

expect fun platformName(): String

fun createApplicationScreenMessage() : String {
  return "Kotlin Rocks on ${platformName()}"
}
```

つづいて各プラットフォーム毎のソースを書いていきます。
Android用のファイル`SharedCode/src/androidMain/kotlin/actual.kt`を作成。

```androidMain/kotlin/actual.kt
package org.kotlin.mpp.mobile

actual fun platformName(): String {
  return "Android"
}
```

iOS用のファイル`SharedCode/src/iosMain/kotlin/actual.kt`を作成。

```iosMain/kotlin/actual.kt
package org.kotlin.mpp.mobile

import platform.UIKit.UIDevice

actual fun platformName(): String {
  return UIDevice.currentDevice.systemName() +
         " " +
         UIDevice.currentDevice.systemVersion
}
```
ここでUIKitが使えます！！

プラットフォーム固有のコードをcommon moduleから呼び出すには`expected`宣言を記述し、`expected`宣言に対応する`actual`実装を各プラットフォーム向けに実装する必要があります。

#####Gradle Scriptsの編集
Androidビルドする際にSharedCodeを含めるようにGradle Scriptsを編集していきます。
まずは`settings.gradle`に`include ':SharedCode'`を追記します。

```settings.gradle
include ':app'
include ':SharedCode'
```

つづいて`SharedCode/build.gradle`を作成し、Scriptsを記載します。
これはAndroidとiOS両方になります。

```SharedCode/build.gradle
apply plugin: 'kotlin-multiplatform'

kotlin {
    targets {
        final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
                              ? presets.iosArm64 : presets.iosX64

        fromPreset(iOSTarget, 'iOS') {
            compilations.main.outputKinds('FRAMEWORK')
        }

        fromPreset(presets.jvm, 'android')
    }

    sourceSets {
        commonMain.dependencies {
            api 'org.jetbrains.kotlin:kotlin-stdlib-common'
        }

        androidMain.dependencies {
            api 'org.jetbrains.kotlin:kotlin-stdlib'
        }
    }
}

// workaround for https://youtrack.jetbrains.com/issue/KT-27170
configurations {
    compileClasspath
}
```
中身についてはこの辺を読むとわかるかと思います。
https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html

#####AndroidからSharedCodeを呼び出す
まず依存関係の追加をするので`app/build.gradle`に`implementation project(':SharedCode')`を追記します。

```app/build.gradle
...//省略

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation project(':SharedCode')
}
```

SharedCodeから受け取ったStringを表示するため、`app/src/main/res/layout/activity_main.xml`のTextViewに対して下記のように変更します。（`android:id="@+id/main_text"`があれば大丈夫です。)

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <TextView
            android:id="@+id/main_text"
            android:textSize="42sp"
            android:textAlignment="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>
```

最後に`/app/src/main/java/<package>/MainActivity.kt`に実際に呼び出すコードを書きます。

```MainActivity.kt
package <package>

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView

import org.kotlin.mpp.mobile.createApplicationScreenMessage

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<TextView>(R.id.main_text).text = createApplicationScreenMessage()
    }
}
```
`import org.kotlin.mpp.mobile.createApplicationScreenMessage`と`createApplicationScreenMessage()`が呼び出しコードです。

#####Android実行！
ここまで出来たら実行してみましょう。
`Kotlin Rocks on Android`と表示されるはずです。
<img width="300" alt="Android" src="https://qiita-image-store.s3.amazonaws.com/0/257063/ef0a2ae2-b955-da2f-1a33-374126201bf7.png">

###iOS編
#####プロジェクト作成
iOS側も通常通りXcodeでプロジェクトを作成します。
こちらも最小構成でやっていくのでSingle View Appを選択してLanguageはSiwftにします。（Objective-Cでも可能らしいですが試してません）
プロジェクトの作成場所はどこでも大丈夫ですが、チュートリアルに合わせて、Android側のプロジェクトルートフォルダ内にnativeフォルダを作成してその中に作ります。

#####frameworkの作成
AndroidStudioに戻り、SharedCode/build.gradleに下記コードを追記してiOS用のframeworkを生成していきます。

```SharedCode/build.gradle
...//省略

task packForXCode(type: Sync) {
    final File frameworkDir = new File(buildDir, "xcode-frameworks")
    final String mode = System.getenv('CONFIGURATION')?.toUpperCase() ?: 'DEBUG'

    inputs.property "mode", mode
    dependsOn kotlin.targets.iOS.compilations.main.linkTaskName("FRAMEWORK", mode)

    from { kotlin.targets.iOS.compilations.main.getBinary("FRAMEWORK", mode).parentFile }
    into frameworkDir

    doLast {
        new File(frameworkDir, 'gradlew').with {
            text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
            setExecutable(true)
        }
    }
}

tasks.build.dependsOn packForXCode
```

つづいて、GradleツールウィンドウからSharedCodeをビルドします。
Gradleツールウィンドウが表示されていない方は、
View→Tool Windows→Gradleと選択すると表示されます。
SharedCodeを選択して上部にあるGradleアイコンを選択します。
<img width="445" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/257063/c621de02-bc2f-39d4-d573-db4a1a9c8477.png">

Run Gradle Taskというウィンドウが開かれるので、
Gradle projectが`<project>:SharedCode`となっているのを確認し、
Command lineに`:SharedCode:packForXCode`と入力してOK。
これでtaskが実行され、`SharedCode/build/xcode-frameworks`に`SharedCode.framework`が生成されているはずです。
<img width="721" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/257063/804dcfef-ff85-5448-5819-e2df92ef16f4.png">
AndroidStudioからiOS用のframeworkを作るのはこの最初だけでこれ以降はXcodeからiOSアプリをビルドする時に実行されるようにあとで設定します。

#####XcodeでのBuild設定
生成したSharedCode.frameworkをEmbedded Binariesに追加していきます。
Xcodeにもどり、プロジェクトを選択してさらにGeneralを選択します。
下の方にスクロールしEmbedded Binariesのところの「＋」を選択して表示されたウィンドウのAdd other...から`SharedCode/build/xcode-frameworks/SharedCode.framework`を選択します。
追加すると下図のようになります。
<img width="600" alt="スクリーンショット 2018-10-14 18.21.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/257063/6a229295-6e3e-5240-6d10-67e588644a08.png">

続いてプロジェクトのBitcodeの設定を無効にする必要があります。
Build Settingsに移動しAllを選択します。
Enable Bitcodeを探します。検索欄に「bitcode」と入力すると簡単に見つけられます。
見つけたら設定値をNoにします。
<img width="600" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/257063/c35ba086-0677-6adc-b865-954fd5176acd.png">

Frameworkの場所をXcodeに登録する必要があるので、同じく`Build SettingsのFramework Search Paths`にframeworkの相対pathを入力します。
同じく検索欄にsearchと入力すると見つけやすいです。
この手順通りにやっている場合は`$(SRCROOT)/../../SharedCode/build/xcode-frameworks`と入力します。
<img width="600" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/257063/ef1d599d-f7f8-3454-95ac-69c47e54e847.png">

今後はXcodeからBuildするときにframeworkも更新されるようにScriptを登録します。
Build Phasesに移動し左上の「＋」を選択して`new Run Script Phase`を選択します。
Shellのしたの入力スペースに下記コードを入力します。

```sh
cd "$SRCROOT/../../SharedCode/build/xcode-frameworks"
./gradlew :SharedCode:packForXCode
```
<img width="600" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/257063/5c709fb2-9b48-128d-a18c-89ff406eb98e.png">

最後にRun Scriptを一番上（Target Dependeniesの下）にドラックで移動します。
<img width="600" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/257063/c5c17fcd-b501-08d0-608d-d7e5861ceec8.png">

#####SwiftからSharedCodeを呼び出す
ViewController.swiftにSharedCodeを呼び出すコードを書きます。

```ViewController.swift
import UIKit
import SharedCode

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 21))
        label.center = CGPoint(x: 160, y: 285)
        label.textAlignment = .center
        label.font = label.font.withSize(25)
        label.text = CommonKt.createApplicationScreenMessage()
        view.addSubview(label)
    }
}
```
`import SharedCode`と`CommonKt.createApplicationScreenMessage()`が呼び出しコードです。

#####iOS実行！
Xcodeで通常通り実行してみましょう。
`Kotlin Rocks on iOS 12.0`と表示されるはずです。
<img width="300" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/257063/e4482d79-4f75-71db-ba38-753dce7e07b1.png">

#最後に
これでKotlin/Nativeで両OS動かすことが出来ました。
基本的にはAndroidStuioでコードを書いていく形になるのかなと思います。
iOSだけであればAppCodeを利用すればpluginが用意されているのでもっと楽にはじめられるかと思います。
長くなりましたが、参考になれば幸いです。
Kotlinは書きやすくていい言語だと思うので流行ればいいなと思います。