DroidKaigiアプリの Kotlin Multiplatform - Speaker Deck
Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

DroidKaigiアプリの Kotlin Multiplatform

takahirom
March 27, 2019

DroidKaigiアプリの Kotlin Multiplatform

takahirom

March 27, 2019
Tweet

More Decks by takahirom

Other Decks in Programming

Transcript

  1. Kotlin Multiplatformを導⼊しやすい構成にする 1 Ktor-Client + kotlinx.serializationを使う • Androidで定番のHTTP APIの呼び出し処理を⽣成してくれ るRetrofitはKotlin

    Multiplatformに対応していない • → Ktor-ClientというKotlin Multiplatformで使えるHTTP ク ライアントを使う。 https://github.com/ktorio/ktor より
  2. Kotlin Multiplatformを導⼊しやすい構成にする 1 Ktor-Client + kotlinx.serializationを使う • 同様にgsonやmoshiではなく、Kotlin/kotlinx.serialization というjsonオブジェクトマッパーを使う @Serializable

    data class SponsorItemResponseImpl( override val id: Int, override val name: String, override val url: String, override val image: String ) : SponsorItemResponse アノテーションを付ける
  3. Kotlin Multiplatformを導⼊しやすい構成にする 1 Ktor-Client + kotlinx.serializationを使う override suspend fun getSessions():

    Response { val rawResponse = httpClient.get<String> { url("$apiEndpoint/timetable") accept(ContentType.Application.Json) } return Json.nonstrict.parse(ResponseImpl.serializer(), rawResponse) } コードのイメージ
  4. Kotlin Multiplatformを導⼊しやすい構成にする 1 Ktor-Client + kotlinx.serializationを使う override suspend fun getSessions():

    Response { val rawResponse = httpClient.get<String> { url("$apiEndpoint/timetable") accept(ContentType.Application.Json) } return Json.nonstrict.parse(ResponseImpl.serializer(), rawResponse) } (使い勝⼿はRetrofitのほうがいいです) コードのイメージ
  5. Kotlin Multiplatformを導⼊しやすい構成にする 3 Modelで使うクラスはKotlin Multiplatformで使えるも のだけにする @AndroidParcelize data class SpeechSession(

    override val id: String, override val dayNumber: Int, override val startTime: DateTime, override val endTime: DateTime, … : AndroidParcel { DroidKaigiの1つのセッションを表すクラス
  6. Kotlin Multiplatformを導⼊しやすい構成にする 3 Modelで使うクラスはKotlin Multiplatformで使えるも のだけにする @AndroidParcelize data class SpeechSession(

    override val id: String, override val dayNumber: Int, override val startTime: DateTime, override val endTime: DateTime, … : AndroidParcel { 継承元interfaceを platform毎に変更することで AndroidのParcelableを Kotlin Multiplatformでも 利⽤できるようにする
  7. Kotlin Multiplatformを導⼊しやすい構成にする 3 Modelで使うクラスはKotlin Multiplatformで使えるも のだけにする @AndroidParcelize data class SpeechSession(

    override val id: String, override val dayNumber: Int, override val startTime: DateTime, override val endTime: DateTime, … : AndroidParcel { 継承元interfaceを platform毎に変更することで AndroidのParcelableを Kotlin Multiplatformでも 利⽤できるようにする 詳しくは1つ前の発表のAAkiraさんの以下の記事が最⾼です Kotlin Multiplatform環境でKotlin SerializationとAndroid ExtensionsのParcelize Annotationを使う actual interface AndroidParcel : Parcelable • androidMain内
  8. Kotlin Multiplatformを導⼊しやすい構成にする 3 Modelで使うクラスはKotlin Multiplatformで使えるも のだけにする @AndroidParcelize data class SpeechSession(

    override val id: String, override val dayNumber: Int, override val startTime: DateTime, override val endTime: DateTime, … AndroidParcel { ← Klockという  Kotlin Multiplatformで  使える⽇付や時間の  ライブラリを使う
  9. Kotlin Multiplatformを導⼊しやすい構成にする 3 Modelで使うクラスはKotlin Multiplatformで使えるも のだけにする @AndroidParcelize data class SpeechSession(

    override val id: String, override val dayNumber: Int, override val startTime: DateTime, override val endTime: DateTime, … AndroidParcel { ← Klockという  Kotlin Multiplatformで  使える⽇付や時間の  ライブラリを使う 今後はJetbrainsが⽇付などのライブラリを 開発予定らしい
  10. DroidKaigiの構成 
 (Kotlin MPP特化版) "OESPJE.PEVMFT BQJ NPEFM Kotlin Multi Platform

    Project JPTDPNCJOFE J041SPKFDU kotlin.srcDirsで
 他のモジュールを
 参照する
  11. DroidKaigiのAPIのモジュール @Binds abstract fun DroidKaigiApi(impl: InjectableKtorDroidKaigiApi): DroidKaigiApi • main/の中では普通にAndroidのコードが書けるので、
 うまく継承したりしてInjectしてあげる

    class InjectableKtorDroidKaigiApi @Inject constructor( httpClient: HttpClient, @Named("apiEndpoint") apiEndpoint: String ) : KtorDroidKaigiApi(httpClient, apiEndpoint, null)
  12. iOSのSwiftのコードから Kotlinのsuspend functionが 呼べない問題 override fun getSessions( callback: (response: Response)

    -> Unit, onError: (error: Exception) -> Unit ) { GlobalScope.launch(requireNotNull(coroutineDispatcherForCallback)) { try { val response = getSessions() callback(response) } catch (ex: Exception) { onError(ex) } } } • Kotlin Coroutinesのコードをコールバックを使って ラップするメソッドを作成 成功時と失敗時のコールバック
  13. iOSのSwiftのコードから Kotlinのsuspend functionが 呼べない問題 override fun getSessions( callback: (response: Response)

    -> Unit, onError: (error: Exception) -> Unit ) { GlobalScope.launch(requireNotNull(coroutineDispatcherForCallback)) { try { val response = getSessions() callback(response) } catch (ex: Exception) { onError(ex) } } } • Kotlin Coroutinesのコードをコールバックを使って ラップするメソッドを作成 Coroutinesをlaunch
  14. iOSのSwiftのコードから Kotlinのsuspend functionが 呼べない問題 override fun getSessions( callback: (response: Response)

    -> Unit, onError: (error: Exception) -> Unit ) { GlobalScope.launch(requireNotNull(coroutineDispatcherForCallback)) { try { val response = getSessions() callback(response) } catch (ex: Exception) { onError(ex) } } } • Kotlin Coroutinesのコードをコールバックを使って ラップするメソッドを作成 既存のsuspend functionを呼び出す
  15. iOSのSwiftのコードから Kotlinのsuspend functionが 呼べない問題 override fun getSessionsAsync(): Deferred<Response> = GlobalScope.async(requireNotNull(coroutineDispatcherForCallback))

    { getSessions() } Kotlin Multiplatform Module側に
 Deferredを返すメソッドを作っておく SwiftでDeferredをasSingle()でRxSwiftのSingleに変換する func fetch() -> Single<SessionContents> { return ApiComponentKt.generateDroidKaigiApi() .getSessionsAsync() .asSingle(Response.self) .map { ResponseToModelMapperKt.toModel($0) } .catchError { throw handledKotlinException($0) }
  16. iOSのSwiftのコードから Kotlinのsuspend functionが 呼べない問題 override fun getSessionsAsync(): Deferred<Response> = GlobalScope.async(requireNotNull(coroutineDispatcherForCallback))

    { getSessions() } Kotlin Multiplatform Module側に
 Deferredを返すメソッドを作っておく SwiftでDeferredをasSingle()でRxSwiftのSingleに変換する func fetch() -> Single<SessionContents> { return ApiComponentKt.generateDroidKaigiApi() .getSessionsAsync() .asSingle(Response.self) .map { ResponseToModelMapperKt.toModel($0) } .catchError { throw handledKotlinException($0) } asSingle()の中⾝は?
  17. iOSのSwiftのコードから Kotlinのsuspend functionが 呼べない問題 extension Kotlinx_coroutines_core_nativeDeferred { func asSingle<ElementType>(_ elementType:

    ElementType.Type) -> Single<ElementType> { return Single<ElementType>.create { observer in self.invokeOnCompletion { cause in if let cause = cause { observer(.error(cause)) return KotlinUnit() } if let result = self.getCompleted() as? ElementType { observer(.success(result)) return KotlinUnit() } • asSingle()はKotlinx_coroutines_core_nativeDeferredに対する extension functionになっている
  18. iOSのSwiftのコードから Kotlinのsuspend functionが 呼べない問題 extension Kotlinx_coroutines_core_nativeDeferred { func asSingle<ElementType>(_ elementType:

    ElementType.Type) -> Single<ElementType> { return Single<ElementType>.create { observer in self.invokeOnCompletion { cause in if let cause = cause { observer(.error(cause)) return KotlinUnit() } if let result = self.getCompleted() as? ElementType { observer(.success(result)) return KotlinUnit() } Deferred#invokeOnCompletionを呼び出すことで Coroutinesを起動できる • asSingle()はKotlinx_coroutines_core_nativeDeferredに対する extension functionになっている
  19. APIの環境の切り替え iOSもあるので、AndroidのBuildConfigだけではダメ。 internal expect fun apiEndpoint(): String common Android internal

    actual fun apiEndpoint(): String = BuildConfig.API_ENDPOINT iOS data/api-impl/src/iosMain/kotlinDebug/ /ApiEndpoint.kt internal actual fun apiEndpoint(): String = “https://.../api” data/api-impl/src/iosMain/kotlinRelease/ /ApiEndpoint.kt internal actual fun apiEndpoint(): String = “https://.../api”
  20. Android Studio上で Kotlin Multiplatform ModuleのクラスがUnresolved referenceになる 基本的には ・enableFeaturePreview(“GRADLE_METADATA") をsettings.gradleに記述 (これはGradle

    6.0からデフォルトになるらしい。) ・Gradleのバージョンを4.7にすることで解決。 GRADLE_METADATAとは?
  21. Gradle Module Metadataとは 参考 Introducing Gradle Module Metadataより https://blog.gradle.org/gradle-metadata-1.0 •

    Gradle 5.3で1.0になった機能。 • .moduleで終わるjsonのファイル • 例えば今までのJavaのバージョンが
 pomファイルから分からなかったが、
 .moduleで分かるようになったりする。 • Kotlin/Nativeでは別々のアーキテクチャによって
 別のバイナリを使ったりできる