Slide 1

Slide 1 text

Shibuya.apk #43 ©2023 RAKUS Co., Ltd. Compose MultiPlatformで 始めるクロスプラットフォーム開発 ライブラリ編 @akkiee76 Shibuya.apk #43

Slide 2

Slide 2 text

Shibuya.apk #43 Akihiko Sato / 株式会社ラクス Lead Engineer / @akkiee76 SaaS 開発 (Backend, Frontend) / Mobile 開発 (iOS, Android) 上流工程、コードレビュー、チームの課題改善など コールドブリューコーヒー☕ / パン作り🍞 / あんバターフランス🥐 自己紹介

Slide 3

Slide 3 text

Shibuya.apk #43 今日伝えたいこと Compose MultiPlatform で開発で主軸となる ライブラリの紹介

Slide 4

Slide 4 text

Shibuya.apk #43 Kotlin Multiplatform for Mobile (KMM) JetBrainsが開発したKotlinベースのクロスプラットフォーム 開発フレームワーク。1つの共通のKotlinコードベースを使用して、 iOSとAndroidの両方のプラットフォーム向けにネイティブアプリを 開発することができる点が特徴。

Slide 5

Slide 5 text

Shibuya.apk #43 Kotlin Multiplatform for Mobile (KMM) フレームワークイメージ

Slide 6

Slide 6 text

Shibuya.apk #43 Kotlin Multiplatform for Mobile (KMM) アーキテクチャイメージ Compose Multiplatform

Slide 7

Slide 7 text

Shibuya.apk #43 Compose MultiPlatform Compose Multiplatformは、Kotlinを使用して複数のプラットフォーム間でUI を共有するための宣言的なフレームワークです。JetBrainsとオープンソースの貢献 者によって開発され、Jetpack Composeに基づいています。 https://github.com/JetBrains/compose-multiplatform

Slide 8

Slide 8 text

Shibuya.apk #43 ここからは導入手順を紹介します

Slide 9

Slide 9 text

Shibuya.apk #43 開発に必要な環境 ・ macOS ・ Android Studio ・ Xcode ・ JDK ・ Kotlin Multiplatform Mobile plugin ・ Kotlin plugin ・ CocoaPods dependency manager あとは brew install kdoctor を実行するだけ

Slide 10

Slide 10 text

Shibuya.apk #43 プロジェクト作成 * Desktop版はIntelliJから作成可能

Slide 11

Slide 11 text

Shibuya.apk #43 プロジェクト作成(Compose Multiplatform Wizard) https://terrakok.github.io/Compose-Multiplatform-Wizard/

Slide 12

Slide 12 text

Shibuya.apk #43 プロジェクトビルド androidx.compose.material3 に対応

Slide 13

Slide 13 text

Shibuya.apk #43 基本的なライブラリ選定 16種類のライブラリが選択可能

Slide 14

Slide 14 text

Shibuya.apk #43 開発の主軸になるライブラリの紹介 ・ Voyager (navigation) ・ Ktrofit (HTTP client) ・ Koin (dependency injection) ・ SQLDelight (SQL)

Slide 15

Slide 15 text

Shibuya.apk #43 navigation 事情 navigation-composeはサポートされていなかった。。 https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Navigation

Slide 16

Slide 16 text

Shibuya.apk #43 Voyager Screen クラスを継承して Content を作成します。 class PostListScreen : Screen { @Composable override fun Content() { // ... } } data class PostDetailsScreen(val postId: Long) : Screen { @Composable override fun Content() { // ... } }

Slide 17

Slide 17 text

Shibuya.apk #43 Voyager LocalNavigator.currentOrThrow を使用して画面遷移することが可能。 class PostListScreen : Screen { @Composable override fun Content() { // ... } @Composable private fun PostCard(post: Post) { val navigator = LocalNavigator.currentOrThrow Card( modifier = Modifier.clickable { navigator.push(PostDetailsScreen(post.id)) // Also works: } ) { // ... } } }

Slide 18

Slide 18 text

Shibuya.apk #43 Voyager TopAppBar、BottomNavigationにも対応しており、 navigation-composeのように実装することで実現可能。 @Composable override fun Content() { Navigator(HomeScreen) { navigator -> Scaffold( topBar = { /* ... */ }, content = { CurrentScreen() }, bottomBar = { /* ... */ } ) } }

Slide 19

Slide 19 text

Shibuya.apk #43 Ktrofit RetrofitのようなAPIで 実装することが可能。 https://foso.github.io/Ktorfit/ interface UnsplashService { @GET("search/photos") suspend fun searchPhotos( @Query("query") query: String, @Query("page") page: Int = 1, @Query("per_page") perPage: Int = 1, @Query("client_id") clientId: String = "clientId" ): UnsplashSearchResponse companion object { private const val BASE_URL = "https://api.unsplash.com/" fun create(): UnsplashService { return ktorfit { baseUrl(BASE_URL) httpClient(HttpClient { install(ContentNegotiation) { json(Json { isLenient = true; ignoreUnknownKeys = true }) } }) converterFactories( FlowConverterFactory(), CallConverterFactory() ) }.create() } } }

Slide 20

Slide 20 text

Shibuya.apk #43 Ktrofit (serialization) レスポンスをserializeするため、 kotlinx-serializationも同時に導入します。 https://github.com/Kotlin/kotlinx.serialization import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class UnsplashUser( val id: String, val name: String, val username: String, val bio: String?, @SerialName("profile_image") val profileImage: UnsplashProfileImage?, )

Slide 21

Slide 21 text

Shibuya.apk #43 Koin (dependency injection) injectionするmoduleを定義します。 iOSとAndroidのそれぞれで定義する必要があります。 https://insert-koin.io/docs/reference/koin-mp/kmp/ private val searchViewModel = module { singleOf(::SearchViewModel) } private val searchRepository = module { singleOf(::SearchRepository) } fun appModule() = listOf(searchViewModel, searchRepository) // for iOS fun initKoin() { startKoin { modules(appModule()) } } // for Android fun initKoin(appDeclaration: KoinAppDeclaration = {}) = startKoin { appDeclaration() modules(appModule()) }

Slide 22

Slide 22 text

Shibuya.apk #43 Koin (dependency injection) Android Applicationクラスに初期化実装を行います。 https://insert-koin.io/docs/reference/koin-mp/kmp/ class MainApplication: Application(), KoinComponent { override fun onCreate() { super.onCreate() initKoin { androidContext(this@MainApplication) } } } class MainActivity : AppCompatActivity() { private val searchViewModel: SearchViewModel by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MainView(searchViewModel) } } }

Slide 23

Slide 23 text

Shibuya.apk #43 Koin (dependency injection) iOS Appクラスに初期化実装を行います。 https://insert-koin.io/docs/reference/koin-mp/kmp/ @main struct iOSApp: App { init() { AppModuleKt.initKoin() } var body: some Scene { WindowGroup { // content } } } struct ContentView: View { private let searchViewModel = AppModule().getSearchViewModel() var body: some View { // content } }

Slide 24

Slide 24 text

Shibuya.apk #43 .sqファイルでクエリを定義することで、 実装コードからSQLが実行可能になります。 SQLDelight (SQL) selectAll: SELECT * FROM hockeyPlayer; insert: INSERT INTO hockeyPlayer(player_number, full_name) VALUES (?, ?); insertFullPlayerObject: INSERT INTO hockeyPlayer(player_number, full_name) VALUES ?; val database = Database(driver) val playerQueries: PlayerQueries = database.playerQueries println(playerQueries.selectAll().executeAsList()) // Prints [HockeyPlayer(15, "Ryan Getzlaf")] playerQueries.insert(player_number = 10, full_name = "Corey Perry") println(playerQueries.selectAll().executeAsList()) // Prints [HockeyPlayer(15, "Ryan Getzlaf"), HockeyPlayer(10, "Corey Perry")] val player = HockeyPlayer(10, "Ronald McDonald") playerQueries.insertFullPlayerObject(player)

Slide 25

Slide 25 text

Shibuya.apk #43 まとめ ・ Compose Multiplatform Wizard を利用することで、 簡単にライブラリ設定ができる! ・ まだまだ使い勝手が悪い点もあるが今後のアップデートに期待!

Slide 26

Slide 26 text

Shibuya.apk #43 ご静聴ありがとうございました