Slide 1

Slide 1 text

Kotlin Multiplatform 始めました DroidKaigi.collect { #15@Fukuoka } mikan( 一瀬喜弘)

Slide 2

Slide 2 text

自己紹介 object Mikan { val name = " 一瀬喜弘" val company = "karabiner.tech" val work = Engineer.Android val hobby = listOf( " 漫画", " アニメ", " ゲーム", " 折り紙", "OSS 開発・コントリビュート", ) }

Slide 3

Slide 3 text

年末から​ iOS/Android で​ 共通する​ ロジックを​ KMP で​ 作る​ タスクを​ 受け​ 持ったので、​ 既存の​ アプリに​ KMP を​ 導入しようとした​ ときに​ 遭遇した​ ことを​ 共有しようと​ 思います

Slide 4

Slide 4 text

その​ 1 KMP の​ モジュールどうやって​ 作れば​ いいんや!

Slide 5

Slide 5 text

DroidKaigi2024 のカンファレンスアプリを作っていたときの記憶 「KMP プラグイン入れてる状態でnew module したらKMP 製のモジュールを作る選択肢があったはず」 ↓ おもむろにnew module する ↓ ない。 。

Slide 6

Slide 6 text

最近Android Studio(Canary 版) にKMP モジュールを生やす機能が追加されたからその影響で消えたのか?

Slide 7

Slide 7 text

ないのはどうしようもないので、 「 File>New>New Project 」を選び Kotlin Multiplatform Library を選択 生成されたファイルをコピペしてkmp モジュールを作りました

Slide 8

Slide 8 text

補足: あとで既存のプロジェクトをマルチプラットフォームにするチュートリアルを見つけた https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-integrate-in-existing-app.html#make- your-code-cross-platform File | New | New Module Java or Kotlin Library を選択 ルートディレクトリの build.gradle.kts を手で書き換える モジュールディレクトリの build.gradle.kts を手で書き換える → すごいアナログなやり方だった

Slide 9

Slide 9 text

その​ 2 Swift 向けには​ どの​ 形式(Regular framework/XCFramework) で​ 出力するように​ したら​ いいんだ??

Slide 10

Slide 10 text

Regular framework とXCFramework 、どっちを選べばいいんだ??。 。 ↓ 出力したバイナリはSwift Package Manager 経由で取り込むからそれに都合いいほうがいいんだよなー ↓ 「kmp spm 」でググる ↓ 「おっ、なんかそれっぽい記事が公式にあるやん」 ↓ 「ふむふむ、このチュートリアルではXCFramework を使ってるのね」 ↓ 「じゃあ、XCFramework でいいや」

Slide 11

Slide 11 text

iOS integration methods https://kotlinlang.org/docs/multiplatform-ios-integration-overview.html このドキュメントを読んだらiOS に統合するやり方として、 Local integration と Remote integration の 2 種類の方法があって、 Local integration ではRegular framework 、 Remote integration ではXCFramework を選択していた。 。 どっちでもいけるのか、 、 じゃあちゃんと善し悪し調べないといけないな と思ったのがこの資料作り始めたときだったのでだれか教えてください

Slide 12

Slide 12 text

その​ 3 KMP の​ 実装書いたけど​ どうやって​ Swift の​ 世界に​ 持っていけばいい??

Slide 13

Slide 13 text

やりかた 1. XCFramework を生成する ./gradlew assembleSharedDebugXCFramework 2. SPM の依存に追加する .binaryTarget( name: "KmpShared", path: "/relative/path/to/kmp-module/build/XCFrameworks/debug/shared.xcframework" ), 3. iOS アプリをビルドする 4. import shared するとKMP のが提供するAPI が使えるようになる shared の部分はビルド設定に依存する it.binaries.framework { baseName = "shared" // <- // ...

Slide 14

Slide 14 text

→ iOS アプリをビルドせずとも、これやってもできるって教えてもらったけど、XCode で参照してるObj-C のヘ ッダファイルの更新がなんかうまいこといかんかったので結局ビルドしてる xcrun xcodebuild -resolvePackageDependencies -project .xcodeproj

Slide 15

Slide 15 text

脱線 AWS の​ JWT が​ HTTP ヘッダーの​ 仕様違反してるせいで​ 再認証するの​ めんどくさい​ ことなったんだけど!?

Slide 16

Slide 16 text

KMP で利用予定のエンドポイントはJWT(JSON Web Token) 認証を使う JWT トークンには寿命があり、切れたら401 でレスポンスが返ってくる リフレッシュすることでトークンが自動更新され通信が続行できる

Slide 17

Slide 17 text

Ktor にはリフレッシュの機構が付いてる refreshTokens { runBlocking { val token = jwtApiClient.issueJwtToken().jwt BearerTokens( token, "", ) } } HttpClient(OkHttp) { install(Auth) { bearer { loadTokens { runBlocking { val token = jwtApiClient.issueJwtToken().jwt BearerTokens( token, "", ) } } }

Slide 18

Slide 18 text

KMP で利用予定のエンドポイントはJWT(JSON Web Token) 認証を使うことになっている JWT トークンには寿命があり、切れたら401 で返ってくる リフレッシュすることでトークンが自動更新され通信が続行できる Ktor のリフレッシュの機構書いたけどリフレッシュしてくれない。 。 エラー内容を読むとヘッダーのパースに失敗しているみたい

Slide 19

Slide 19 text

原因: www-authenticate ヘッダーがHTTP の仕様を違反していた https://datatracker.ietf.org/doc/html/rfc7235#page-7 User agents are advised to take special care in parsing the field value, as it might contain more than one challenge, and each challenge can contain a comma-separated list of authentication parameters. ASIS www-authenticate: Bearer scope="" error="invalid_token" error_description="the token has expired" TOBE www-authenticate: Bearer scope="", error="invalid_token", error_description="the token has expired"

Slide 20

Slide 20 text

バックエンドに修正できるか聞いてみた ↓ これはAWS のJWT authorizer が返してるやつだから修正できないですねー ↓ 成す術無し ↓ しょうがないので、トークン取得の際に、取得した時刻を持っておいて、次回API 通信するときに前回の通信 から一定時間経過していたらまずはトークンの更新を行うという処理を挟んだ

Slide 21

Slide 21 text

公式ドキュメントでもばっちりスペース区切りになっている。 。

Slide 22

Slide 22 text

その​ 4 Swift から​ 呼び出しやすい​ API 設計を​ 考えなければいけない。 。

Slide 23

Slide 23 text

年末年始は下記の記事を見ながらKMP の実装を書く場合に気をつけなければいけないことを勉強していた https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce

Slide 24

Slide 24 text

呼び出すときにパラメータ名の省略がしたければ @ObjCName("_") っていうアノテーションを付ける Kotlin: 名前が同じであってもパッケージ名が異なれば定義可能、Obj-C/Swift: パッケージ名などない 同名のクラスを定義してしまうと、片方は名前衝突を回避するためにアンダースコアがつけられる Item, Item_ Collection(List, Set, Map) のシグネチャにインターフェースを入れる( Set ) と、Swift から 見たときは Set になる interface を使うと苦しむことになりやすい 拡張関数を作っても、Swift から呼び出すときは ExampleKt.extensionFun(reciever) みたいな感じで残 念なことになる Kotlin が例外をthrow して、Swift でdo-try-cathc したのにcache がスルーされてクラッシュする 関数に @Throws(IOException::class) のようにアノテートしないと、NSError を吐いてくれない Coroutine は @MainActor で実行しなければいけないという制約がある

Slide 25

Slide 25 text

ご静聴ありがとうございました https://kotlinlang.org/docs/native-spm.html#set-up-remote-integration https://kotlinlang.org/docs/multiplatform-ios-integration-overview.html https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-integrate-in-existing- app.html#make-your-code-cross-platform https://medium.com/@aoriani/list/writing-swiftfriendly-kotlin-multiplatform-apis-c51c2b317fce