Slide 1

Slide 1 text

Kotlin Multiplatform Project入門 @_a_akira 荒谷 光 Kotlin Fest 2019 あらたに あきら

Slide 2

Slide 2 text

About me @_a_akira AAkira M3, Inc. Akira Aratani https://aakira.app !"#$%&'()*+,-./0123456789 ✈⊿ https://aakira.studio aakira.studio

Slide 3

Slide 3 text

Kotlin Multiplatform Projectと私 • Kotlin/Nativeチュートリアル Android, iOS編
 http://aakira.app/blog/2018/10/kotlin-native • Kotlin Multiplatform構想 ~今やる理由編~
 https://aakira.app/blog/2018/12/kotlin-mpp-reason • Kotlin Multiplatform構想 ~設計編~
 https://aakira.app/blog/2018/12/kotlin-mpp-architecture • Kotlin Multiplatform環境でKotlin Serializationと
 Android ExtensionsのParcelize Annotationを使う
 http://aakira.app/blog/2018/12/kotlin-mpp-android-parcelable • NapierというKotlin Multiplatform用のログライブラリを作った
 https://aakira.app/blog/2019/02/napier

Slide 4

Slide 4 text

Kotlin Multiplatform Projectと私 Timber likeなKotlin mpp用ライブラリ https://github.com/AAkira/Napier

Slide 5

Slide 5 text

Agenda 1. Kotlin Multiplatform Projectとは 1.1. Kotlin Multipratform Projectについて 1.2. Kotlin/Native 1.3. Kotlin/JS 1.4. Kotlin Multiplatform Projectの仕組み 1.5. メリット、デメリット 2. 実践Kotlin Multiplatform Project 2.1. ライブラリ選定 2.2. 設計を考える 2.3. サンプルアプリを作る 3. まとめ

Slide 6

Slide 6 text

Kotlin Multiplatform Projectとは

Slide 7

Slide 7 text

クロスプラットフォームの歴史

Slide 8

Slide 8 text

クロスプラットフォームの歴史 React Native Flutter Xamarin Unity Java2ObjC Titanium Mobile PhoneGap Adobe Air

Slide 9

Slide 9 text

クロスプラットフォームツールとは  • UI含む全てのコードを共通化(独自UIを使用) • UI含む全てのコードを共通化(ネイティブUIを使用) • ロジックのみ共通化 アプローチ方法が異なる

Slide 10

Slide 10 text

UI含む全てのコードを共通化(独自UI) • PhoneGap(Cordova)
 - HTML & JS based
 - Adobe, Apache • Flutter
 - Dart
 - Google
 - OpenGLを使って低レイヤにUIを描画

Slide 11

Slide 11 text

UI含む全てのコードを共通化(ネイティブUI) • Xamarin Forms
 - C# 等の .NET言語
 - Microsoft • React Native
 - JSベース
 - Facebook

Slide 12

Slide 12 text

ロジックのみ • Java2ObjC
 - Java
 - Google
 - Kotlin/Nativeに置き換えられそう • Xamarin(Native)
 - C#等の.NET言語
 - Microsoft •Kotlin Multiplatform Project

Slide 13

Slide 13 text

Kotlin Multiplatform Projectとは UI 部分は提供せずに
 ロジック部分のみ を共通化する Kotlin Multiplatform Project では

Slide 14

Slide 14 text

Kotlin Multiplatform Projectとは 全てのコードを共通で管理したい React Native Flutter Xamarin Forms

Slide 15

Slide 15 text

Kotlin Multiplatform Projectとは そもそも Kotlin Multiplatform Project とは?

Slide 16

Slide 16 text

Kotlin Multiplatform Projectとは Kotlin/Native ≠ Multiplatform Project

Slide 17

Slide 17 text

Kotlin Multiplatform Projectとは Kotlin/Native ⊂ Multiplatform Project

Slide 18

Slide 18 text

Android Native JS JVM

Slide 19

Slide 19 text

Kotlin Multiplatform Projectとは •Kotlin/JVM (Android, Server) •Kotlin/Native (iOS, Windows, Linux, macOS ...etc) •Kotlin/JS の総称

Slide 20

Slide 20 text

Kotlin Multiplatform Projectとは Multiplatform Projectの頭文字を取って 
 MPP と呼ぶ(公式)

Slide 21

Slide 21 text

MPPとは 既に2012年1月の時点でThe Road Aheadという構想を発表している
 https://blog.jetbrains.com/kotlin/2012/01/the-road-ahead

Slide 22

Slide 22 text

MPPとは Kotlin/JSのサンプル
 https://github.com/abreslav/kotlin-js-hello

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

MPPとは AAkira < とはいえ... AAkira < Kotlin/Nativeもまだアレだったし
      キワモノだろう...

Slide 26

Slide 26 text

https://www.youtube.com/watch?v=UyTBXEZ983g

Slide 27

Slide 27 text

Kotlin/JVM

Slide 28

Slide 28 text

Kotlin/JVM • 特に説明は無い • 普段のイメージしているKotlinのことを指す • MPPの観点から見ると
 Server, Androidがこれにあたる

Slide 29

Slide 29 text

Kotlin/Native

Slide 30

Slide 30 text

Kotlin/Native • LLVM Toolchainを使用して
 各プラットフォームのバイナリを作成 • VM環境が不要

Slide 31

Slide 31 text

プラットフォーム 対応アーキテクチャ iOS arm32, arm64, simulator x86/64 MacOS x86_64 Android NDK arm32, arm64 Windows mingw x86_64, x86 Linux x86_64, arm32, arm64, MIPS,
 MIPS little endian, Raspberry Pi WebAssembly wasm32 Kotlin/Native https://kotlinlang.org/docs/reference/native-overview.html#target-platforms

Slide 32

Slide 32 text

iOS arm32, arm64, simulator x86/64 MacOS x86_64 ndroid NDK arm32, arm64 Windows mingw x86_64, x86 Linux x86_64, arm32, arm64, MIPS,
 MIPS little endian, Raspberry Pi ebAssembly wasm32 Kotlin/Native

Slide 33

Slide 33 text

Kotlin/Native Kotlin/Native ≠ iOS

Slide 34

Slide 34 text

Kotlin/Native - 歴史 Version 日付 主な変更 Kotlin Version 0.1 2017/04 Early Previewリリース 1.1.x 0.2 2017/05 Coroutineのサポート 1.1.x 0.3 2017/06 Android, Windowsのサポート 1.1.x 0.4 2017/10 iOS, macOSのサポート 1.2 0.5 2017/12 Objective-C, SwiftからのKotlin呼び出し 1.2 0.6 2018/02 Kotlin Multiplatform対応 1.2.20 0.7 2018/02 Objective-C, Swiftの相互運用性、multithreadまわりの強化 1.2.x 0.8 2018/07 stdlibがKotlin/JVMとKotlin/JSに, ios Arm32サポート 1.2.x 0.9 2018/09 安定版Coroutine等 主にKotlin1.3のアップデートに追従 1.3.M2 Beta 2018/09 Kotlin1.3リリースに伴いBeta版に 1.3 1.1.0 2018/12 パフォーマンス改善, Contracts対応 1.3 1.2.0(1) 2019/04 Windows32bitサポート, WindowsとMacからLinuxへのクロスコンパイル 1.3.30 1.3.0 2019/06 Linux Arm64サポート, メモリ管理の大幅改善 1.3.40

Slide 35

Slide 35 text

Kotlin/Native - 互換性 ,PUMJO 4XJGU 0CKFDUJWF$ class class @interface interface protocol @protocol @Throws throws error:(NSError**)error null nil nil Unit return type Void void String String NSString String NSMutableString NSMutableString List Array NSArray MutableList NSMutableArray NSMutableArray Set Set NSSet MutableSet NSMutableSet NSMutableSet Map Dictionary NSDictionary MutableMap NSMutableDictionary NSMutableDictionary https://kotlinlang.org/docs/reference/native/objc_interop.html

Slide 36

Slide 36 text

Kotlin/Native - 互換性 package com.github.aakira fun hoge() { } HogeKt.hoge() Hoge.kt ViewController.swift

Slide 37

Slide 37 text

Kotlin/Native - 互換性 package com.github.aakira fun hoge() { } HogeKt.hoge() Hoge.kt ViewController.swift

Slide 38

Slide 38 text

Kotlin/Native - 互換性なし • suspend関数(Coroutine) • inlineクラス • Kotlinのコレクションインタフェースを実装した独自クラス • Objective-Cのクラスを継承したKotlinのクラス IUUQTHJUIVCDPN+FU#SBJOTLPUMJOOBUJWFCMPCNBTUFS0#+$@*/5&301NEVOTVQQPSUFE

Slide 39

Slide 39 text

Kotlin/Native - Freeze FreezeされていないObjectは
 別スレッドで操作することが出来ない

Slide 40

Slide 40 text

Kotlin/Native - Freeze Primitive型, Enum, Singleton Object... FreezeされていないObjectは
 別スレッドで操作することが出来ない Frozen objects Immutable objects

Slide 41

Slide 41 text

Kotlin/Native - Freeze Actual.kt ViewController.swift class Hoge {} fun foo(block: (Hoge) -> Unit) { val hoge = Hoge() block(hoge) } ActualKt.foo { hoge in DispatchQueue.global(qos: .background).async { print(hoge) } }

Slide 42

Slide 42 text

Kotlin/Native - Freeze Actual.kt ViewController.swift class Hoge {} fun foo(block: (Hoge) -> Unit) { val hoge = Hoge() block(hoge) } ActualKt.foo { hoge in DispatchQueue.global(qos: .background).async { print(hoge) } }

Slide 43

Slide 43 text

class Hoge {} fun foo(block: (Hoge) -> Unit) { val hoge = Hoge() block(hoge) } Kotlin/Native - Freeze Actual.kt ViewController.swift ActualKt.foo { hoge in DispatchQueue.global(qos: .background).async { print(hoge) } } kotlin.native.IncorrectDereferenceException

Slide 44

Slide 44 text

Kotlin/Native - Freeze Actual.kt ViewController.swift ActualKt.foo { hoge in DispatchQueue.global(qos: .background).async { print(hoge) } } class Hoge {} fun foo(block: (Hoge) -> Unit) { val hoge = Hoge() block(hoge) } Immutableを保証

Slide 45

Slide 45 text

Kotlin/Native - Freeze Actual.kt ViewController.swift ActualKt.foo { hoge in DispatchQueue.global(qos: .background).async { print(hoge) } } class Hoge {} fun foo(block: (Hoge) -> Unit) { val hoge = Hoge().freeze() block(hoge) } Immutableを保証

Slide 46

Slide 46 text

fun bar(block: (Int) -> Unit) { val hoge = 46 block(hoge) } Kotlin/Native - Freeze Actual.kt ViewController.swift ActualKt.bar { hoge in DispatchQueue.global(qos: .background).async { print(hoge) } }

Slide 47

Slide 47 text

fun bar(block: (Int) -> Unit) { val hoge = 46 block(hoge) } Kotlin/Native - Freeze Actual.kt ViewController.swift ActualKt.bar { hoge in DispatchQueue.global(qos: .background).async { print(hoge) } } Primitive型は問題ない

Slide 48

Slide 48 text

Kotlin/Native - 成果物 成果物 詳細 EXECUTABLE 実行ファイル KLIBRARY Kotlin/Native library(*.klib) FRAMEWORK Objective-Cのフレームワーク(*.framework) DYNAMIC 動的リンクライブラリ STATIC 静的リンクライブラリ https://kotlinlang.org/docs/reference/native-overview.html

Slide 49

Slide 49 text

Kotlin/JS

Slide 50

Slide 50 text

Kotlin/JS • Kotlinで書いたコードをJavaScriptに変換する • DOM(Document Object Model)の操作、WebGL,
 Node.jsを使ったサーバサイドJSも利用可能

Slide 51

Slide 51 text

Kotlin/JS • JavaScriptは動的型付け言語 • Kotlinは静的型付け言語 • Kotlin => JavaScriptへの変換で型情報は失われる • TypeScriptは将来的に対応予定 (優先度低)

Slide 52

Slide 52 text

Kotlin/JS - DOM import kotlin.browser.document fun main() { document.body?.textContent = "Hello world!" } /web/Main.kt

Slide 53

Slide 53 text

Kotlin/JS - DOM import kotlin.browser.document fun main() { document.body?.textContent = "Hello world!" } /web/Main.kt

Slide 54

Slide 54 text

Kotlin/JS - DOM /web/build/mpp-web.js (function (root, factory) { if (typeof define === 'function' && define.amd) define(['exports', 'kotlin'], factory); else if (typeof exports === 'object') factory(module.exports, require('kotlin')); else { if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'mpp-web'. Its dependency 'kotlin' was not found. Please,
 check whether 'kotlin' is loaded prior to 'mpp-web'."); } root['mpp-web'] = factory(typeof this['mpp-web'] === 'undefined' ? {} : this['mpp-web'], kotlin); } }(this, function (_, Kotlin) { 'use strict'; function main() { var tmp$; (tmp$ = document.body) != null ? (tmp$.textContent = 'Hello world!') : null; } var package$com = _.com || (_.com = {}); var package$aakira = package$com.aakira || (package$com.aakira = {}); var package$mpp = package$aakira.mpp || (package$aakira.mpp = {}); var package$web = package$mpp.web || (package$mpp.web = {}); package$web.main = main; main(); return _; }));

Slide 55

Slide 55 text

Kotlin/JS - DOM (function (root, factory) { if (typeof define === 'function' && define.amd) define(['exports', 'kotlin'], factory); else if (typeof exports === 'object') factory(module.exports, require('kotlin')); else { if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'mpp-web'. Its dependency 'kotlin' was not found. Please,
 check whether 'kotlin' is loaded prior to 'mpp-web'."); } root['mpp-web'] = factory(typeof this['mpp-web'] === 'undefined' ? {} : this['mpp-web'], kotlin); } }(this, function (_, Kotlin) { 'use strict'; function main() { var tmp$; (tmp$ = document.body) != null ? (tmp$.textContent = 'Hello world!') : null; } var package$com = _.com || (_.com = {}); var package$aakira = package$com.aakira || (package$com.aakira = {}); var package$mpp = package$aakira.mpp || (package$aakira.mpp = {}); var package$web = package$mpp.web || (package$mpp.web = {}); package$web.main = main; main(); return _; })); /web/build/mpp-web.js

Slide 56

Slide 56 text

throw new Error("Error loading module 'mpp-web'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'mpp-web'."); } root['mpp-web'] = factory(typeof this['mpp-web'] === 'undefined' ? {} : this['mpp-web'], kotlin); } }(this, function (_, Kotlin) { 'use strict'; function main() { var tmp$; (tmp$ = document.body) != null ? (tmp$.textContent = 'Hello world!') : null; } var package$com = _.com || (_.com = {}); Kotlin/JS - DOM /web/build/mpp-web.js

Slide 57

Slide 57 text

throw new Error("Error loading module 'mpp-web'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'mpp-web'."); } root['mpp-web'] = factory(typeof this['mpp-web'] === 'undefined' ? {} : this['mpp-web'], kotlin); } }(this, function (_, Kotlin) { 'use strict'; function main() { var tmp$; (tmp$ = document.body) != null ? (tmp$.textContent = 'Hello world!') : null; } var package$com = _.com || (_.com = {}); Kotlin/JS - DOM /web/build/mpp-web.js

Slide 58

Slide 58 text

Kotlin/JS - DOM fun main() { document.body?.textContent = "Hello world!" } function main() { var tmp$; (tmp$ = document.body) != null ? (tmp$.textContent = 'Hello world!') : null; }

Slide 59

Slide 59 text

Kotlin/JS - external DOMだけではなく JavaScriptのライブラリも読み込み可能

Slide 60

Slide 60 text

Kotlin/JS - external external class Logger { companion object { fun log(log: String) } } fun main() { Logger.log("Hello logger world!") } /web/Main.kt

Slide 61

Slide 61 text

Kotlin/JS - external external class Logger { companion object { fun log(log: String) } } fun main() { Logger.log("Hello logger world!") } JavaScriptのライブラリ /web/Main.kt

Slide 62

Slide 62 text

Kotlin/JS - external external class Logger { companion object { fun log(log: String) } } fun main() { Logger.log("Hello logger world!") } external修飾子 /web/Main.kt

Slide 63

Slide 63 text

Kotlin/JS - external external class Logger { companion object { fun log(log: String) } } fun main() { Logger.log("Hello logger world!") } 生成されるJavaScriptはどうなるか /web/Main.kt

Slide 64

Slide 64 text

Kotlin/JS - external (function (root, factory) { if (typeof define === 'function' && define.amd) define(['exports', 'kotlin'], factory); else if (typeof exports === 'object') factory(module.exports, require('kotlin')); else { if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'mpp-web'. Its dependency 'kotlin' was not found. Please,
 check whether 'kotlin' is loaded prior to 'mpp-web'."); } root['mpp-web'] = factory(typeof this['mpp-web'] === 'undefined' ? {} : this['mpp-web'], kotlin); } }(this, function (_, Kotlin) { 'use strict'; function main() { Logger.log('Hello logger world!'); } var package$com = _.com || (_.com = {}); var package$aakira = package$com.aakira || (package$com.aakira = {}); var package$mpp = package$aakira.mpp || (package$aakira.mpp = {}); var package$web = package$mpp.web || (package$mpp.web = {}); package$web.main = main; main(); return _; })); /web/build/mpp-web.js

Slide 65

Slide 65 text

(function (root, factory) { if (typeof define === 'function' && define.amd) define(['exports', 'kotlin'], factory); else if (typeof exports === 'object') factory(module.exports, require('kotlin')); else { if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'mpp-web'. Its dependency 'kotlin' was not found. Please,
 check whether 'kotlin' is loaded prior to 'mpp-web'."); } root['mpp-web'] = factory(typeof this['mpp-web'] === 'undefined' ? {} : this['mpp-web'], kotlin); } }(this, function (_, Kotlin) { 'use strict'; function main() { Logger.log('Hello logger world!'); } var package$com = _.com || (_.com = {}); var package$aakira = package$com.aakira || (package$com.aakira = {}); var package$mpp = package$aakira.mpp || (package$aakira.mpp = {}); var package$web = package$mpp.web || (package$mpp.web = {}); package$web.main = main; main(); return _; })); Kotlin/JS - external /web/build/mpp-web.js

Slide 66

Slide 66 text

if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'mpp-web'. Its dependency 'ko check whether 'kotlin' is loaded prior to 'mpp-web'."); } root['mpp-web'] = factory(typeof this['mpp-web'] === 'undefined' ? {} } }(this, function (_, Kotlin) { 'use strict'; function main() { Logger.log('Hello logger world!'); } var package$com = _.com || (_.com = {}); var package$aakira = package$com.aakira || (package$com.aakira = {}); var package$mpp = package$aakira.mpp || (package$aakira.mpp = {}); var package$web = package$mpp.web || (package$mpp.web = {}); package$web.main = main; main(); return _; Kotlin/JS - external /web/build/mpp-web.js

Slide 67

Slide 67 text

if (typeof kotlin === 'undefined') { throw new Error("Error loading module 'mpp-web'. Its dependency 'ko check whether 'kotlin' is loaded prior to 'mpp-web'."); } root['mpp-web'] = factory(typeof this['mpp-web'] === 'undefined' ? {} } }(this, function (_, Kotlin) { 'use strict'; function main() { Logger.log('Hello logger world!'); } var package$com = _.com || (_.com = {}); var package$aakira = package$com.aakira || (package$com.aakira = {}); var package$mpp = package$aakira.mpp || (package$aakira.mpp = {}); var package$web = package$mpp.web || (package$mpp.web = {}); package$web.main = main; main(); return _; Kotlin/JS - external /web/build/mpp-web.js ラップしたLoggerは生成されていない

Slide 68

Slide 68 text

Kotlin/JS - dynamic dynamic型

Slide 69

Slide 69 text

Kotlin/JS - dynamic dynamic型 val hoge var hoge 型がわからない

Slide 70

Slide 70 text

Kotlin/JS - dynamic Any? dynamic ≒

Slide 71

Slide 71 text

Kotlin/JS - dynamic /web/Main.kt external class Logger { val hoge: dynamic fun log(log: String) } fun main() { val logger = Logger() val hoge = logger.hoge as String logger.log(hoge) }

Slide 72

Slide 72 text

Kotlin/JS - dynamic /web/Main.kt external class Logger { val hoge: dynamic fun log(log: String) } fun main() { val logger = Logger() val hoge = logger.hoge as String logger.log(hoge) }

Slide 73

Slide 73 text

Kotlin/JS - dynamic /web/Main.kt external class Logger { val hoge: dynamic fun log(log: String) } fun main() { val logger = Logger() val hoge = logger.hoge as String logger.log(hoge) }

Slide 74

Slide 74 text

Kotlin/JS - dynamic /web/Main.kt external class Logger { val hoge: dynamic fun log(log: String) } fun main() { val logger = Logger() val hoge = logger.hoge as String logger.log(hoge) }

Slide 75

Slide 75 text

Kotlin/JS - dynamic /web/Main.kt external class Logger { val hoge: dynamic fun log(log: String) } fun main() { val logger = Logger() val hoge = logger.hoge as String logger.log(hoge) } var hoge = typeof (tmp$ = logger.hoge) === 'string' ? tmp$ : throwCCE(); /web/build/mpp-web.js

Slide 76

Slide 76 text

Kotlin/JS - dynamic /web/Main.kt external class Logger { val hoge: dynamic fun log(log: String) } fun main() { val logger = Logger() val hoge = logger.hoge as String logger.log(hoge) } var hoge = typeof (tmp$ = logger.hoge) === 'string' ? tmp$ : throwCCE(); /web/build/mpp-web.js

Slide 77

Slide 77 text

Kotlin/JS - 成果物 成果物 詳細 plain グローバルスコープに定義される
 デフォルトはPlainになっている amd 主にクライアントサイドで使われる
 非同期にロードしやすい commonjs Node.jsなどサーバサイドで使われる事が多い umd AMDとCommonJSの両方をサポートしている https://kotlinlang.org/docs/reference/js-modules.html

Slide 78

Slide 78 text

MPPのメリット・デメリット

Slide 79

Slide 79 text

MPPのメリット MPPは敢えてUIの共通化は行っていない React Native Flutter MPP UIは共通 UIはプラットフォーム毎

Slide 80

Slide 80 text

MPPのメリット ネイティブアプリと同じ言語 JavaScript Dart Kotlin

Slide 81

Slide 81 text

Android Native JS JVM 全てのプラットフォームを共通化出来る

Slide 82

Slide 82 text

MPPのメリット Domain Objectを共通化出来る UserModel data class User( val id: String, val gender: String?, val generation: String )

Slide 83

Slide 83 text

MPPのメリット Domain Objectを共通化出来る UserModel data class User( val id: String, val gender: String?, val generation: String )

Slide 84

Slide 84 text

MPP(クロスプラットフォーム)のメリット • 認証系を共通化 • ログ送信基盤を共通化 • 広義の意味でのUtilityを共通化 ロジックの共通化が可能

Slide 85

Slide 85 text

MPP(クロスプラットフォーム)のメリット ロジックの共通化が可能 • 認証系を共通化 • ログ送信基盤を共通化 • 広義の意味でのUtilityを共通化 Kotlin Fest 2019 69WIAVXI6buoHunGhub1J542wI3l1o3RqTqB9OgPZJM= Have a nice Kotlin

Slide 86

Slide 86 text

MPP(クロスプラットフォーム)のメリット • 認証系を共通化 • ログ送信基盤を共通化 • 広義の意味でのUtilityを共通化 ロジックの共通化が可能

Slide 87

Slide 87 text

MPP(クロスプラットフォーム)のメリット • 認証系を共通化 • ログ送信基盤を共通化 • 広義の意味でのUtilityを共通化 ロジックの共通化が可能 PM < Dimension(Key)の値が
 register-userとregistered-user
 の2つあるのですが...? iOSer, Webmen < registered-userやろ!
 Androider < あっ...ほんま...
 ごめんて...

Slide 88

Slide 88 text

MPP(クロスプラットフォーム)のメリット • 認証系を共通化 • ログ送信基盤を共通化 • 広義の意味でのUtilityを共通化 ロジックの共通化が可能

Slide 89

Slide 89 text

MPP(クロスプラットフォーム)のメリット • 認証系を共通化 • ログ送信基盤を共通化 • 広義の意味でのUtilityを共通化 ロジックの共通化が可能 QA < Androidは "残り1分"
 iOSは "残り60秒"
 と表示されるのですが...? iOSer < どっちも正しい!!!
 Androider < どっちも正しい!!!

Slide 90

Slide 90 text

MPPのメリット • Android, サーバで広く使われているKotlinを使用できる • Andorid, iOSのコードだけでなく、
 Web(JavaScript, wasm), サーバ のコードまでも共有することが可能 • 最初にGradle等の設定さえすれば、新しくフレームワークの記法を
 覚える必要がない • 他のクロスプラットフォームツールではAndroidの方がバグが多いが、
 Kotlin/NativeではAndroid側が今までと変わらず開発出来る

Slide 91

Slide 91 text

MPPのデメリット • Javaの資産が使えない • iOSでCoroutineがメインスレッドしか使えない(1.3.40現在) • .frameworkを1つしか読み込めない(1.3.40現在) • ライブラリのKotlin versionを揃えないといけない(1.3.40現在) • 全てを共通化する場合iOS, WEBのエンジニアの理解が必要

Slide 92

Slide 92 text

Kotlin Multiplatform Projectの仕組み

Slide 93

Slide 93 text

MPPの仕組み 共通モジュールで生成された成果物を 各プラットフォームから参照する

Slide 94

Slide 94 text

MPPの仕組み • Android, Server
 JVM言語でGradle使っていれば、通常の外部ライブラリと同じ • iOS
 .frameworkを作成してXcodeで読み込む • WEB
 生成されたJavaScriptファイルを読み込む 共通モジュールで生成された成果物を 各プラットフォームから参照する

Slide 95

Slide 95 text

MPPの作り方 1. 共通モジュールの作成 • スライド内ではCommonモジュールと呼ぶ
 (名前は自由に変更可能) • 共通モジュール = Gradleのライブラリモジュール 2. Gradleの設定 • プラグインの読み込み • アーティファクトの指定

Slide 96

Slide 96 text

MPPの作り方 1. 共通モジュールの作成 • スライド内ではCommonモジュールと呼ぶ
 (名前は自由に変更可能) • 共通モジュール = Gradleのライブラリモジュール 2. Gradleの設定 • プラグインの読み込み • アーティファクトの指定 以上!

Slide 97

Slide 97 text

MPPの仕組み 全ての共通化は難しい

Slide 98

Slide 98 text

MPPの仕組み 全ての共通化は難しい Expect, Actual でプラットフォーム間の差異を解決

Slide 99

Slide 99 text

MPPの仕組み 抽象オブジェクト(≒Abstract) Expect Actual 具象オブジェクト(≒Override) クラス、関数、プロパティ、アノテーション等全てに付けられる

Slide 100

Slide 100 text

ExpectとActual /common/src/commonMain/kotlin/com/github/mpp/common/Common.kt fun hello(): String { return "Hello, ${platformString()}" } expect fun platformString(): String 共通モジュールに定義

Slide 101

Slide 101 text

ExpectとActual /common/src/androidMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Android!" /common/src/iosMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello iOS!" /common/src/jsMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Web!" /common/src/jvmMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Server!"

Slide 102

Slide 102 text

ExpectとActual /common/src/androidMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Android!" /common/src/iosMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello iOS!" /common/src/jsMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Web!" /common/src/jvmMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Server!"

Slide 103

Slide 103 text

ExpectとActual /common/src/androidMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Android!" /common/src/iosMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello iOS!" /common/src/jsMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Web!" /common/src/jvmMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Server!"

Slide 104

Slide 104 text

ExpectとActual /common/src/androidMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Android!" /common/src/iosMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello iOS!" /common/src/jsMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Web!" /common/src/jvmMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Server!"

Slide 105

Slide 105 text

ExpectとActual /common/src/androidMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Android!" /common/src/iosMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello iOS!" /common/src/jsMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Web!" /common/src/jvmMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Server!"

Slide 106

Slide 106 text

ExpectとActual /common/src/androidMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Android!" /common/src/iosMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello iOS!" /common/src/jsMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Web!" /common/src/jvmMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Server!"

Slide 107

Slide 107 text

ExpectとActual /common/src/androidMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Android!" /common/src/iosMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello iOS!" /common/src/jsMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Web!" /common/src/jvmMain/kotlin/com/github/mpp/common/Actual.kt actual fun platformString() = "Hello Server!" それぞれのディレクトリに定義が必要

Slide 108

Slide 108 text

ExpectとActual 全て実装してないと、コンパイラがエラーで教えてくれる

Slide 109

Slide 109 text

実践Kotlin Multiplatform Project

Slide 110

Slide 110 text

ライブラリ選定 ジャンル ライブラリ URL )551 LUPS DMJFOU IUUQTHJUIVCDPNLUPSJPLUPS 4FSJBMJ[FS LPUMJOTFSJBMJ[BUJPO IUUQTHJUIVCDPNLPUMJOLPUMJOYTFSJBMJ[BUJPO 3%# 42-%FMJHIU IUUQTHJUIVCDPNTRVBSFTRMEFMJHIU ,74 NVMUJQMBUGPSNTFUUJOHT IUUQTHJUIVCDPNSVTTIXPMGNVMUJQMBUGPSNTFUUJOHT %* ,PEFJO IUUQTHJUIVCDPN,PEFJO'SBNFXPSL,PEFJO%* *0 ,PUMJO*0 IUUQTHJUIVCDPN,PUMJOLPUMJOYJP %BUF ,MPDL IUUQTHJUIVCDPNLPSMJCTLMPDL -PHHFS /BQJFS IUUQTHJUIVCDPN""LJSB/BQJFS

Slide 111

Slide 111 text

ライブラリ選定 https://github.com/AAkira/Kotlin-Multiplatform-Libraries

Slide 112

Slide 112 text

設計を考える - Client • 公式ではMVP(Model-View-Presenter)が推奨されている
 Kotlin Confのアプリもこの構成 • どこまでコードを共有するかが難しい • 個人的に現段階ではMVVM+Layered Architectureで
 疎結合にしておく方が良いかもしれない

Slide 113

Slide 113 text

Kotlin Multiplatform 7JFX 7JFX.PEFM 3FQPTJUPSZ %# "1*$MJFOU
 H31$FUDʜ 3FQPTJUPSZ -PDBM $BDIF "1*$MJFOU
 )551FUDʜ 4FSWJDF 4FSWJDF Platform Reactive Stream Coroutine

Slide 114

Slide 114 text

設計を考える - Server • 現状MPPで全て作るならKtor一択 • Domain Objectだけを共有するなら
 Spring Bootとかでも良いかも • マイクロサービス構成なら
 BFF(Backends For Frontends)サーバを作るのがオススメ

Slide 115

Slide 115 text

Backend (Micro services) BFF Server Client Kotlin Multiplatform

Slide 116

Slide 116 text

MPPサンプルアプリ

Slide 117

Slide 117 text

No content

Slide 118

Slide 118 text

JSONを返す Greeting Model { "hello":"Hello world from Server!" }

Slide 119

Slide 119 text

Greeting Model { "hello":"Hello world from Server!" }

Slide 120

Slide 120 text

Greeting Model { "hello":"Hello world from Server!" }

Slide 121

Slide 121 text

Greeting Model { "hello":"Hello world from Server!" }

Slide 122

Slide 122 text

ライブラリ ジャンル ライブラリ URL 4FSWFS 'SBNFXPSL ,UPSTFSWFS IUUQTHJUIVCDPNLUPSJPLUPS )551$MJFOU ,UPSDMJFOU IUUQTHJUIVCDPNLUPSJPLUPS 4FSJBMJ[FS LPUMJOTFSJBMJ[BUJPO IUUQTHJUIVCDPNLPUMJOLPUMJOYTFSJBMJ[BUJPO "TZOD $PSPVUJOF IUUQTHJUIVCDPN,PUMJOLPUMJOYDPSPVUJOFT

Slide 123

Slide 123 text

MPP - パッケージ構成 ├── android │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com.github.aakira.mpp │ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── jsMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ios ├── js │ └── build.gradle ├── server │ └── build.gradle └── settings.gradle

Slide 124

Slide 124 text

MPP - パッケージ構成 ├── android │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com.github.aakira.mpp │ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── jsMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ios ├── js │ └── build.gradle ├── server │ └── build.gradle └── settings.gradle

Slide 125

Slide 125 text

├── android │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com.github.aakira.mpp │ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle MPP - パッケージ構成

Slide 126

Slide 126 text

│ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ios ├── js │ └── build.gradle ├── server │ └── build.gradle └── settings.gradle MPP - パッケージ構成

Slide 127

Slide 127 text

│ ├── AndroidManifest.xml │ ├── java │ │ └── com.github.aakira.mpp │ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── jsMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle MPP - パッケージ構成

Slide 128

Slide 128 text

│ ├── AndroidManifest.xml │ ├── java │ │ └── com.github.aakira.mpp │ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── jsMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle MPP - パッケージ構成

Slide 129

Slide 129 text

│ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common MPP - パッケージ構成

Slide 130

Slide 130 text

│ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── jsMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ios ├── js │ └── build.gradle ├── server │ └── build.gradle MPP - パッケージ構成

Slide 131

Slide 131 text

│ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin DD MPP - パッケージ構成

Slide 132

Slide 132 text

│ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin MPP - パッケージ構成

Slide 133

Slide 133 text

│ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── jsMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat MPP - パッケージ構成

Slide 134

Slide 134 text

│ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin MPP - パッケージ構成

Slide 135

Slide 135 text

MPP - Gradle設定 apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosMain { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } } jsMain { dependencies { implementation rootProject.ext.kotlinJs implementation rootProject.ext.coroutineJs implementation rootProject.ext.serializationJs implementation rootProject.ext.ktorClientJs implementation rootProject.ext.ktorClientJsonJs } } jvmMain { dependencies { implementation rootProject.ext.kotlinJvm implementation rootProject.ext.coroutine implementation rootProject.ext.serialization implementation rootProject.ext.ktorClientJvm implementation rootProject.ext.ktorClientJsonJvm } } } } /common/build.gradle

Slide 136

Slide 136 text

MPP - Gradle設定 /common/build.gradle apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon

Slide 137

Slide 137 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定 MPP用プラグインをApply /common/build.gradle

Slide 138

Slide 138 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定 Kotlin1.3.0より前 • kotlin-platform-common • kotlin-platform-android • org.jetbrains.kotlin.platform.native • kotlin-platform-js /common/build.gradle

Slide 139

Slide 139 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定 Tips: Android用のgradleは別で定義 /common/build.gradle

Slide 140

Slide 140 text

apply plugin: 'kotlin-multiplatform' apply plugin: 'com.android.library' android { compileSdkVersion 28 buildToolsVersion "28.0.3" defaultConfig { minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } kotlin { android() MPP - Gradle設定 /common/build.gradle Android用の定義を書かなければならない

Slide 141

Slide 141 text

apply plugin: 'kotlin-multiplatform' apply plugin: 'com.android.library' android { compileSdkVersion 28 buildToolsVersion "28.0.3" defaultConfig { minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } kotlin { android() MPP - Gradle設定 /common/build.gradle Android用の定義を書かなければならない

Slide 142

Slide 142 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosMain { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } } jsMain { dependencies { implementation rootProject.ext.kotlinJs implementation rootProject.ext.coroutineJs implementation rootProject.ext.serializationJs implementation rootProject.ext.ktorClientJs implementation rootProject.ext.ktorClientJsonJs } } jvmMain { dependencies { implementation rootProject.ext.kotlinJvm implementation rootProject.ext.coroutine implementation rootProject.ext.serialization implementation rootProject.ext.ktorClientJvm implementation rootProject.ext.ktorClientJsonJvm } } } } apply plugin: 'com.android.library' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 28 buildToolsVersion "28.0.3" defaultConfig { minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), ɹɹɹɹɹɹɹɹɹ'proguard-rules.pro' } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } MPP - Gradle設定 /common/android.gradle /common/build.gradle

Slide 143

Slide 143 text

apply plugin: 'kotlin-multiplatform' apply plugin: 'com.android.library' android { compileSdkVersion 28 buildToolsVersion "28.0.3" defaultConfig { minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } kotlin { android() MPP - Gradle設定 /common/build.gradle

Slide 144

Slide 144 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定 /common/build.gradle

Slide 145

Slide 145 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP用Platform Pluginを読み込み MPP - Gradle設定 /common/build.gradle

Slide 146

Slide 146 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定 /common/build.gradle

Slide 147

Slide 147 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() if (project.findProperty("device")?.toBoolean() ?: false) { iosArm64('ios') { binaries { framework() } } } else { iosX64('ios') { binaries { framework() } } } MPP - Gradle設定 /common/build.gradle

Slide 148

Slide 148 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() if (project.findProperty("device")?.toBoolean() ?: false) { iosArm64('ios') { binaries { framework() } } } else { iosX64('ios') { binaries { framework() } } } MPP - Gradle設定 /common/build.gradle

Slide 149

Slide 149 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() if (project.findProperty("device")?.toBoolean() ?: false) { iosArm64('ios') { binaries { framework() } } } else { iosX64('ios') { binaries { framework() } } } MPP - Gradle設定 /common/build.gradle

Slide 150

Slide 150 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定 /common/build.gradle

Slide 151

Slide 151 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定 /common/build.gradle

Slide 152

Slide 152 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() { browser() } sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon MPP - Gradle設定 /common/build.gradle

Slide 153

Slide 153 text

apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定 /common/build.gradle

Slide 154

Slide 154 text

} } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosMain { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } MPP - Gradle設定 /common/build.gradle 依存関係の定義

Slide 155

Slide 155 text

} } jvm() js() sourceSets { commonMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.2.2" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.1" implementation "io.ktor:ktor-client-core:1.2.2" implementation "io.ktor:ktor-client-gson:1.2.2" } } androidMain.dependencies { } iosMain { dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.2.2" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:0.11.1" implementation "io.ktor:ktor-client-ios:1.2.2" implementation "io.ktor:ktor-client-json-native:1.2.2" } MPP - Gradle設定 /common/build.gradle ルートにまとめて定義

Slide 156

Slide 156 text

} } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosMain { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } 共通モジュールの依存定義 MPP - Gradle設定 /common/build.gradle

Slide 157

Slide 157 text

/common/build.gradle commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosMain { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } } jsMain { dependencies { implementation rootProject.ext.kotlinJs implementation rootProject.ext.coroutineJs MPP - Gradle設定

Slide 158

Slide 158 text

commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosMain { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } } jsMain { dependencies { implementation rootProject.ext.kotlinJs implementation rootProject.ext.coroutineJs /common/build.gradle MPP - Gradle設定

Slide 159

Slide 159 text

/common/build.gradle apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64('ios') { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon 名前を指定している MPP - Gradle設定

Slide 160

Slide 160 text

/common/build.gradle commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosMain { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } } jsMain { dependencies { implementation rootProject.ext.kotlinJs implementation rootProject.ext.coroutineJs 同じ名前 MPP - Gradle設定

Slide 161

Slide 161 text

/common/build.gradle もし何も指定しない場合 apply plugin: 'kotlin-multiplatform' apply from: 'android.gradle' kotlin { android() iosArm64() { binaries { framework() } } jvm() js() sourceSets { commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon MPP - Gradle設定

Slide 162

Slide 162 text

commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosArm64Main { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } } jsMain { dependencies { implementation rootProject.ext.kotlinJs implementation rootProject.ext.coroutineJs /common/build.gradle フルネームを書く必要がある MPP - Gradle設定

Slide 163

Slide 163 text

/common/build.gradle commonMain { dependencies { implementation rootProject.ext.kotlinCommon implementation rootProject.ext.coroutineCommon implementation rootProject.ext.serializationCommon implementation rootProject.ext.ktorClient implementation rootProject.ext.ktorClientJson } } androidMain.dependencies { } iosMain { dependencies { implementation rootProject.ext.coroutineNative implementation rootProject.ext.serializationNative implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } } jsMain { dependencies { implementation rootProject.ext.kotlinJs implementation rootProject.ext.coroutineJs MPP - Gradle設定

Slide 164

Slide 164 text

/common/build.gradle implementation rootProject.ext.ktorClientIos implementation rootProject.ext.ktorClientJsonIos } } jsMain { dependencies { implementation rootProject.ext.kotlinJs implementation rootProject.ext.coroutineJs implementation rootProject.ext.serializationJs implementation rootProject.ext.ktorClientJs implementation rootProject.ext.ktorClientJsonJs } } jvmMain { dependencies { implementation rootProject.ext.kotlinJvm implementation rootProject.ext.coroutine implementation rootProject.ext.serialization implementation rootProject.ext.ktorClientJvm implementation rootProject.ext.ktorClientJsonJvm } } } MPP - Gradle設定

Slide 165

Slide 165 text

│ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common MPP - Gradle設定

Slide 166

Slide 166 text

│ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ ├── commonMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ ├── ApiClient.kt │ │ └── model │ │ └── Greeting.kt │ ├── iosMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common MPP - Gradle設定

Slide 167

Slide 167 text

├── android │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com.github.aakira.mpp │ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml MPP - Gradle設定

Slide 168

Slide 168 text

├── android │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com.github.aakira.mpp │ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml MPP - Gradle設定

Slide 169

Slide 169 text

versionCode 1 versionName "1.0.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-an } } } dependencies { implementation project(":common") implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$ } /android/build.gradle 共通モジュールの読み込み

Slide 170

Slide 170 text

├── android │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com.github.aakira.mpp │ │ └── MainActivity.kt │ └── res ├── build.gradle ├── common │ ├── android.gradle │ ├── build.gradle │ └── src │ ├── androidMain │ │ ├── AndroidManifest.xml 共通モジュールの読み込み

Slide 171

Slide 171 text

│ │ └── Actual.kt │ ├── jsMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ios ├── js │ └── build.gradle ├── server │ └── build.gradle └── settings.gradle 共通モジュールの読み込み

Slide 172

Slide 172 text

共通モジュールの読み込み plugins { id 'org.jetbrains.kotlin.js' id 'kotlin-dce-js' } kotlin { } [compileKotlinJs, compileTestKotlinJs].each { config -> config.kotlinOptions { moduleKind = 'umd' sourceMap = true metaInfo = true } } dependencies { implementation project(':common') implementation rootProject.ext.kotlinJs } /web/build.gradle

Slide 173

Slide 173 text

} [compileKotlinJs, compileTestKotlinJs].each { config -> config.kotlinOptions { moduleKind = 'umd' sourceMap = true metaInfo = true } } dependencies { implementation project(':common') implementation rootProject.ext.kotlinJs } /web/build.gradle 共通モジュールの読み込み

Slide 174

Slide 174 text

│ │ └── Actual.kt │ ├── jsMain │ │ └── kotlin │ │ └── com.github.aakira.mpp.common │ │ └── Actual.kt │ └── jvmMain │ └── kotlin │ └── com.github.aakira.mpp.common │ └── Actual.kt ├── dependencies.gradle ├── gradle ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ios ├── js │ └── build.gradle ├── server │ └── build.gradle └── settings.gradle 共通モジュールの読み込み

Slide 175

Slide 175 text

plugins { id 'kotlin' id 'application' } group 'com.github.aakira.mpp' version '0.0.1' mainClassName = "io.ktor.server.netty.EngineMain" sourceSets { main.kotlin.srcDirs = main.java.srcDirs = ['src'] main.resources.srcDirs = ['resources'] } dependencies { implementation project(':common') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" def ktor_server_version = "1.2.2" implementation "io.ktor:ktor-server-netty:$ktor_server_version" implementation "io.ktor:ktor-gson:$ktor_server_version" implementation "ch.qos.logback:logback-classic:1.2.3" } /server/build.gradle 共通モジュールの読み込み

Slide 176

Slide 176 text

group 'com.github.aakira.mpp' version '0.0.1' mainClassName = "io.ktor.server.netty.EngineMain" sourceSets { main.kotlin.srcDirs = main.java.srcDirs = ['src'] main.resources.srcDirs = ['resources'] } dependencies { implementation project(':common') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" def ktor_server_version = "1.2.2" implementation "io.ktor:ktor-server-netty:$ktor_server_version" implementation "io.ktor:ktor-gson:$ktor_server_version" implementation "ch.qos.logback:logback-classic:1.2.3" } /server/build.gradle 共通モジュールの読み込み

Slide 177

Slide 177 text

呼び出しコード

Slide 178

Slide 178 text

internal expect val hostName: String internal expect val coroutineDispatcher: CoroutineDispatcher class ApiClient { private val httpClient = HttpClient() fun getGreeting(successCallback: (Greeting) -> Unit, errorCallback: (Exception) -> Unit) { GlobalScope.apply { launch(coroutineDispatcher) { try { val result = httpClient.get { url { protocol = URLProtocol.HTTP host = hostName // expect value port = 8080 } } val greeting = Json.parse(Greeting.serializer(), result) successCallback(greeting) } catch (e: Exception) { errorCallback(e) } } } } } 共通モジュール(expect) /common/ApiClient.kt

Slide 179

Slide 179 text

internal expect val hostName: String internal expect val coroutineDispatcher: CoroutineDispatcher class ApiClient { private val httpClient = HttpClient() fun getGreeting(successCallback: (Greeting) -> Unit, errorCallback: (Exception) -> Unit) { GlobalScope.apply { launch(coroutineDispatcher) { try { val result = httpClient.get { url { protocol = URLProtocol.HTTP host = hostName // expect value port = 8080 } } val greeting = Json.parse(Greeting.serializer(), result) successCallback(greeting) } catch (e: Exception) { errorCallback(e) } } } } } 共通モジュール(expect) /common/ApiClient.kt

Slide 180

Slide 180 text

getGreeting(successCallback: (Greeting) -> Unit, errorCallback: ion) -> Unit) { GlobalScope.apply { launch(coroutineDispatcher) { try { val result = httpClient.get { url { protocol = URLProtocol.HTTP host = hostName // expect value port = 8080 } } val greeting = Json.parse(Greeting.serializer(), result) successCallback(greeting) } catch (e: Exception) { errorCallback(e) } } } /common/ApiClient.kt 共通モジュール(expect)

Slide 181

Slide 181 text

/common/ApiClient.kt 共通モジュール(expect) internal expect val hostName: String internal expect val coroutineDispatcher: CoroutineDispatcher class ApiClient { private val httpClient = HttpClient() fun getGreeting(successCallback: (Greeting) -> Unit, errorCallback (Exception) -> Unit) { GlobalScope.apply { launch(coroutineDispatcher) { try { val result = httpClient.get { url { protocol = URLProtocol.HTTP host = hostName // expect value

Slide 182

Slide 182 text

/common/androidMain/Actual.kt internal actual val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO internal actual val coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default internal actual val coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default /common/jsMain/Actual.kt /common/jvmMain/Actual.kt 共通モジュール(actual)

Slide 183

Slide 183 text

/common/androidMain/Actual.kt internal actual val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO internal actual val coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default internal actual val coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default /common/jsMain/Actual.kt /common/jvmMain/Actual.kt 共通モジュール(actual)

Slide 184

Slide 184 text

internal actual val coroutineDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue()) internal class NsQueueDispatcher(private val dispatchQueue: dispatch_queue_t) : CoroutineDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { dispatch_async(dispatchQueue) { block.run() } } } /common/iosMain/Actual.kt 共通モジュール(actual)

Slide 185

Slide 185 text

internal actual val coroutineDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue()) internal class NsQueueDispatcher(private val dispatchQueue: dispatch_queue_t) : CoroutineDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { dispatch_async(dispatchQueue) { block.run() } } } /common/iosMain/Actual.kt 共通モジュール(actual)

Slide 186

Slide 186 text

internal actual val coroutineDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue()) internal class NsQueueDispatcher(private val dispatchQueue: dispatch_queue_t) : CoroutineDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { dispatch_async(dispatchQueue) { block.run() } } } /common/iosMain/Actual.kt 共通モジュール(actual)

Slide 187

Slide 187 text

/android/MainActivity.kt private val handler = Handler(Looper.getMainLooper()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ApiClient().getGreeting( successCallback = { handler.post { helloText.text = it.hello } }, errorCallback = { handler.post { helloText.text = it.toString() } }) } Client(Android)

Slide 188

Slide 188 text

private val handler = Handler(Looper.getMainLooper()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ApiClient().getGreeting( successCallback = { handler.post { helloText.text = it.hello } }, errorCallback = { handler.post { helloText.text = it.toString() }) } Client(Android) /android/MainActivity.kt

Slide 189

Slide 189 text

/ios/ViewController.swift override func viewDidLoad() { super.viewDidLoad() let label = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.size.width,
 height: view.frame.size.height) ) label.textAlignment = .center label.font = label.font.withSize(26) self.view.addSubview(label) ApiClient().getGreeting( successCallback: { response in label.text = response.hello }, errorCallback: { error in print(error) }) } Client(iOS)

Slide 190

Slide 190 text

) label.textAlignment = .center label.font = label.font.withSize(26) self.view.addSubview(label) ApiClient().getGreeting( successCallback: { response in label.text = response.hello }, errorCallback: { error in print(error) }) } /ios/ViewController.swift Client(iOS)

Slide 191

Slide 191 text

/web/Main.kt fun main() { ApiClient().getGreeting( successCallback = { document.body?.textContent = it.hello }, errorCallback = { console.log(it.toString()) } ) } Client(WEB)

Slide 192

Slide 192 text

fun main() { ApiClient().getGreeting( successCallback = { document.body?.textContent = it.hello }, errorCallback = { console.log(it.toString()) } ) } /web/Main.kt Client(WEB)

Slide 193

Slide 193 text

/web/resources/index.html Mpp Sample ...(ུ) Client(WEB)

Slide 194

Slide 194 text

Mpp Sample ...(ུ)

Slide 195

Slide 195 text

MPPサンプル https://github.com/AAkira/mpp-example

Slide 196

Slide 196 text

まとめ

Slide 197

Slide 197 text

まとめ • MPPは人類が求めていた答え • Gradleのライブラリモジュールとして提供されるので、
 基本的にデメリットは無い • Gradle力が少しだけ必要 • いずれはKotlin/Everywhereに

Slide 198

Slide 198 text

宣伝 - 本を書きました 技術評論社から今秋発売予定 タイトル 未定 Android, Server, Test, Coroutine, MPP を現場のエンジニアが解説 著者: 愛澤、荒谷、木原、仙波、前川

Slide 199

Slide 199 text

Have a nice Kotlin! @_a_akira