Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DroidKaigiアプリの Kotlin Multiplatform

Avatar for takahirom takahirom
March 27, 2019

DroidKaigiアプリの Kotlin Multiplatform

Avatar for takahirom

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では別々のアーキテクチャによって
 別のバイナリを使ったりできる