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

KMP 개발을 위한 알아두면 좋은 라이브러리 소개 / DI 프레임워크 찍먹하기

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Dora Lee Dora Lee
December 11, 2023

KMP 개발을 위한 알아두면 좋은 라이브러리 소개 / DI 프레임워크 찍먹하기

GDG Songdo Devfest 2023 행사에서 발표한 "KMP 개발을 위한 알아두면 좋은 라이브러리 소개, DI 프레임워크 찍먹하기" 의 발표 자료 입니다.

Avatar for Dora Lee

Dora Lee

December 11, 2023

More Decks by Dora Lee

Other Decks in Programming

Transcript

  1. KMP 개발을 위한 알아두면 좋은 라이브러리 소개 / DI 프레임워크

    찍먹하기 Incheon/Songdo 이상훈 | at Studio Product Engineer
  2. INDEX 1. KMP에서 DI 사용하기 + DI 프레임워크 ‘koin’ 사용법

    소개 2-1. KMP shared 모듈에서 사용하기 2-2. KMP shared 모듈에서 주입한 객체를 안드로이드에서 사용하기 2-3. KMP shared 모듈에서 주입한 객체를 iOS (Swift) 에서 사용하기 2. KMP 개발을 위한 카테고리별 라이브러리 소개 네트워킹, 로깅, 환경설정 / 데이터 저장, 권한, 하드웨어, 아키텍처 프레임워크, 크래시리포트, 보안, 파일, 직렬화/역직렬화, 시간, Firebase 총 12개의 카테고리에 맞는 유용한 라이브러리들 소개
  3. Why using Dependency Injection? class AwesomeGDGSongdo constructor() { private val

    conference: Conference init { this.conference = new Conference() } public fun getSpeakers(): List<Speaker> { return this.conference.speakers() } }
  4. Why using Dependency Injection? class AwesomeGDGSongdo constructor() { private val

    conference: Conference init { this.conference = new Conference() } public fun getSpeakers(): List<Speaker> { return this.conference.speakers() } } AwesomeGDGSongdo 클래스는 Conference를 의존성으로 가지게 됨
  5. Why using Dependency Injection? class AwesomeGDGSongdo constructor() { private val

    conference: Conference init { this.conference = new Conference() } public fun getSpeakers(): List<Speaker> { return this.conference.speakers() } } 만약, Conference에 들어가는 요소가 달라지거나, Impl 종류가 달라지는 등의 수정이 생기면 → AwesomeGDGSongdo 클래스 또한 수정 필요
  6. Why using Dependency Injection? class AwesomeGDGSongdo constructor( val conference: Conference

    ) { public fun getSpeakers(): List<Speaker> { return this.conference.speakers() } }
  7. Why using Dependency Injection? class AwesomeGDGSongdo constructor( val conference: Conference

    ) { public fun getSpeakers(): List<Speaker> { return this.conference.speakers() } } 지금은 외부에서 conference를 개발자가 마음대로 주입하여 사용함 → 의존성 줄이기 가능
  8. Why using Dependency Injection? 1. 단위 테스트 짜기 쉬워짐 2.

    코드 가독성 증가 3. 코드 재활용성 증가 4. 객체 간 의존성 줄여줌 5. 유연하게 코드 작성 가능
  9. Introducing ‘Insert Koin’ 안드로이드 뿐만 아니라 멀티플랫폼 환경에서도 DI(Dependency Injection)을

    할 수 있게 해주는 라이브러리 멀티플랫폼 환경에서는 Hilt(Dagger) 등 안드로이드에서 주로 쓰이는 DI 라이브러리를 사용할 수 없음
  10. Koin Method module single factory viewModel get bind class ComponentA()

    class ComponentB(val componentA : ComponentA) val moduleA = module { // ComponentA 싱글톤으로 만들기 single { ComponentA() } } val moduleB = module { // Koin으로 만들어진 모듈로부터 ComponentA 가져오기 (ComponentB도 싱글톤) single { ComponentB(get()) } }
  11. Koin Method module single factory viewModel get bind class ComponentA()

    class ComponentB(val componentA : ComponentA) val moduleA = module { // ComponentA 싱글톤으로 만들기 single { ComponentA() } } val moduleB = module { // Koin으로 만들어진 모듈로부터 ComponentA 가져오기 (ComponentB도 싱글톤) single { ComponentB(get()) } }
  12. Koin Method module single factory viewModel get bind class ComponentA()

    class ComponentB(val componentA : ComponentA) val moduleA = module { // ComponentA 호출할 때마다 새로 생성 factory { ComponentA() } } val moduleB = module { // Koin으로 만들어진 모듈로부터 ComponentA 가져오기 (ComponentB도 싱글톤) single { ComponentB(get()) } }
  13. Koin Method (Android Specific) module single factory viewModel get bind

    class ComponentA() class ComponentB(val componentA : ComponentA) val moduleA = module { // ComponentA 호출할 때마다 새로 생성 factory { ComponentA() } } val moduleB = module { // ExampleViewModel 모듈로 추가 viewModel { ExampleViewModel(get(), get()) } }
  14. Koin Method module single factory viewModel get bind class ComponentA()

    class ComponentB(val componentA : ComponentA) val moduleA = module { // ComponentA 호출할 때마다 새로 생성 factory { ComponentA() } } val moduleB = module { // ExampleViewModel 모듈로 추가 (2개의 파라미터에 koin으로 주입된 모듈 가져오기) viewModel { ExampleViewModel(get(), get()) } }
  15. Koin Method module single factory viewModel get bind // Service

    interface interface Service{ fun doSomething() } // Service Implementation class ServiceImpl() : Service{ fun doSomething() { ... } } val myModule = module { single { ServiceImpl() } bind Service::class }
  16. 사용법 :: Shared Module // platform Module (Shared) val platformModule

    = module { singleOf(::Platform) } // KMP Class Definition expect class Platform() { val name: String }
  17. 사용법 :: Shared Module // iOS actual class Platform actual

    constructor() { actual val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion } // Android actual class Platform actual constructor() { actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}" }
  18. 사용법 :: Android fun appModule() = listOf(commonModule, platformModule) startKoin {

    androidContext(this@MainApplication) androidLogger() modules(appModule() + androidModule) } private val platform: Platform by inject()
  19. 사용법 :: iOS (Swift) MainApp.swift struct iOSApp: App { //

    KMM - Koin Call init() { HelperKt.doInitKoin() } var body: some Scene { WindowGroup { ContentView() } } }
  20. 사용법 :: iOS (Swift) MainApp.swift struct iOSApp: App { //

    KMM - Koin Call init() { HelperKt.doInitKoin() } var body: some Scene { WindowGroup { ContentView() } } } KMP에서 Swift로 빌드될 때 Kotlin에서 init~ 으로 시작할 경우, 키워드 충돌 방지를 위해 do~ 로 시작하게 됨 아까 initKoin으로 선언한 함수가 위 규칙으로 인해 doInitKoin 이 됨
  21. 사용법 :: iOS (Swift) :: 주입된 모듈 사용하기 PlatformModuleHelper.kt //

    iOS Only class PlatformModuleHelper : KoinComponent { private val platform : Platform by inject() fun name() : String = platform.name } ContentView.swift struct ContentView: View { // 만들었던 Helper 클래스 swift에서 불러오기 let platformName = PlatformModuleHelper().name() var body: some View { Text(platformName) } }
  22. Introducing Category 🌎 Networking 📋 Logging 📦 Storage / Preferences

    🚧 Permission 📱Hardware 🏗 Architecture 💥 Crash Reporting 🔐 Security 📄 File (I/O) 🗃 Serializer ⏰ Date / Time Firebase
  23. 🌎 Networking | 마이크로 서비스, 웹 애플리케이션 등을 만들기 위한

    비동기식 프레임워크 Android macOS iOS watchOS tvOS Windows Linux Web ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
  24. 🌎 Networking | Client Example HelloWorld.kt package com.example.kmmktor import io.ktor.client.*

    import io.ktor.client.request.* import io.ktor.client.statement.* class HelloWorld { private val client = HttpClient() suspend fun greeting(): String { val response = client.get("https://google.com/") return response.bodyAsText() } }
  25. 🌎 Networking | Client Example 더더욱, 안드로이드 앱을 만들었던 개발자가

    Retrofit을 경험했다면 ktor 순수 경험 자체는 좋지 않을 수 있음
  26. 🌎 Networking | + ktorfit = 🧡 ktor 클라이언트 코드를

    KSP (Kotlin Symbol Processing)을 통해 안드로이드에서의 Retrofit 처럼 사용할 수 있게 해주는 라이브러리
  27. 🌎 Networking | + ktorfit = 🧡 ktor 클라이언트 코드를

    KSP (Kotlin Symbol Processing)을 통해 안드로이드에서의 Retrofit 처럼 사용할 수 있게 해주는 라이브러리 Android macOS iOS watchOS tvOS Windows Linux Web ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
  28. 🌎 Networking | + ktorfit = 🧡 KtorfitExample.kt @Headers(["Content-Type: application/json"])

    @GET("{user}/comments") suspend fun getComments( @Path("user") user: String, @Query("limit") limit: Int ): List<Comment>
  29. 🌎 Networking | + ktorfit = 🧡 ExampleKtorfit.kt interface ExampleApi

    { @GET("people/1/") suspend fun getPerson(): String } val ktorfit = Ktorfit.Builder().baseUrl("https://your-api-server.dev/api/").build() val exampleApi = ktorfit.create<ExampleApi>() // 실제 사용 val response = exampleApi.getPerson() println(response)
  30. 📋 Logging Kermit the log Info, warning, error, debug 등

    severity에 따라 로깅 기본 태그 지정 Xcode, OS 로깅 등 각 플랫폼에 맞는 빌트인 Logger 지원
  31. 📋 Logging Android macOS iOS watchOS tvOS Windows Linux Web

    ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ Kermit the log
  32. 📦 Storage / Preferences | DataStore https://developer.android.com/jetpack /androidx/releases/datastore androidx.datastore ProtoDataStore

    Protocol Buffer 기반 Type-safety Custom Entity based preferences PreferencesDataStore key/value 기반의 preferences
  33. 📦 Storage / Preferences | DataStore [commonMain] DataStore.kt fun createDataStore(

    producePath: () -> String, ): DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath( corruptionHandler = null, migrations = emptyList(), produceFile = { producePath().toPath() }, ) const val dataStoreFileName = "devfest.preferences_pb"
  34. 📦 Storage / Preferences | DataStore [androidMain] DataStore.kt fun dataStore(context:

    Context): DataStore<Preferences> = createDataStore( producePath = { context.filesDir.resolve(dataStoreFileName).absolutePath } )
  35. 📦 Storage / Preferences | DataStore [iosMain] DataStore.kt fun dataStore():

    DataStore<Preferences> = createDataStore( producePath = { val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory( directory = NSDocumentDirectory, inDomain = NSUserDomainMask, appropriateForURL = null, create = false, error = null, ) requireNotNull(documentDirectory).path + "/$dataStoreFileName" } )
  36. 📦 Storage / Preferences | DataStore [commonMain] Example.kt val NOTI_ENABLED

    = booleanPreferencesKey("is_notification_enabled") suspend fun updateNotificationEnabledStatus(isEnabled: Boolean) { dataStore.edit { preferences -> preferences[NOTI_ENABLED] = isEnabled } } val notificationEnabledStatusFlow: Flow<Boolean?> = dataStore.data .map { preferences -> preferences[NOTI_ENABLED] }
  37. 📦 Storage / Preferences | SQLDelight SQLite (or AnotherDB) for

    Multiplatform SQL 쿼리문을 작성하면 해당 쿼리에 맞는 Entity와 DB로부터 Read, Write 하기 위한 모든 코드가 자동으로 생성됨. 또 IDE (ex. Android Studio)에서 SQLDelight가 생성한 API에 대해서 자동완성, Syntax Highlight 등 지원
  38. 📦 Storage / Preferences | SQLDelight Android macOS iOS watchOS

    tvOS Windows Linux Web ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
  39. 📦 Storage / Preferences | SQLDelight TableDefinition.sq CREATE TABLE hockey_player

    ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, number INTEGER NOT NULL );
  40. 📦 Storage / Preferences | SQLDelight TableDefinition.sq CREATE TABLE hockey_player

    ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, number INTEGER NOT NULL );
  41. 📦 Storage / Preferences | SQLDelight src/main/sqldelight/com/example/sqldelight/hockey/data/Player.sq CREATE TABLE hockeyPlayer

    ( player_number INTEGER PRIMARY KEY NOT NULL, full_name TEXT NOT NULL ); CREATE INDEX hockeyPlayer_full_name ON hockeyPlayer(full_name); INSERT INTO hockeyPlayer (player_number, full_name) VALUES (7, 'Dora Lee'); [스키마 정의하기]
  42. 📦 Storage / Preferences | SQLDelight [commonMain] Database.kt import com.example.Database

    expect class DriverFactory { fun createDriver(): SqlDriver } fun createDatabase(driverFactory: DriverFactory): Database { val driver = driverFactory.createDriver() return Database(driver) } [드라이버 공통 인터페이스]
  43. 📦 Storage / Preferences | SQLDelight [androidMain] Database.kt actual class

    DriverFactory(private val context: Context) { actual fun createDriver(): SqlDriver { return AndroidSqliteDriver(Database.Schema, context, "test.db") } } [드라이버 - 안드로이드]
  44. 📦 Storage / Preferences | SQLDelight [nativeMain] Database.kt actual class

    DriverFactory { actual fun createDriver(): SqlDriver { return NativeSqliteDriver(Database.Schema, "test.db") } } [드라이버 - 네이티브 플랫폼들]
  45. 📦 Storage / Preferences | SQLDelight src/main/sqldelight/com/example/sqldelight/hockey/data/Player.sq selectAll: SELECT *

    FROM hockeyPlayer; insert: INSERT INTO hockeyPlayer(player_number, full_name) VALUES (?, ?); insertFullPlayerObject: INSERT INTO hockeyPlayer(player_number, full_name) VALUES ?; [사용할 쿼리 정의]
  46. 📦 Storage / Preferences | SQLDelight Example.kt fun dbQueryExample(driver: SqlDriver)

    { val database = Database(driver) val playerQueries: PlayerQueries = database.playerQueries println(playerQueries.selectAll().executeAsList()) // [HockeyPlayer(7, "Dora Lee")] playerQueries.insert(player_number = 10, full_name = "Dora Lee 2222") println(playerQueries.selectAll().executeAsList()) // [HockeyPlayer(7, "Dora Lee"), HockeyPlayer(10, "Dora Lee 2222")] val player = HockeyPlayer(1, "Dora Lee 333333") playerQueries.insertFullPlayerObject(player) }
  47. 🚧 Permission | 멀티플랫폼 환경에서 각 플랫폼 별 Runtime Permission을

    간단한 API로 쉽게 핸들링 할 수 있는 라이브러리
  48. 🚧 Permission | Supported Permissions Camera: Permission.CAMERA Gallery: Permission.GALLERY Storage

    read: Permission.STORAGE Storage write: Permission.WRITE_STORAGE Fine location: Permission.LOCATION Coarse location: Permission.COARSE_LOCATION Remote notifications: Permission.REMOTE_NOTIFICATION Audio recording: Permission.RECORD_AUDIO Bluetooth LE: Permission.BLUETOOTH_LE Bluetooth Scan: Permission.BLUETOOTH_SCAN Bluetooth Connect: Permission.BLUETOOTH_CONNECT Bluetooth Advertise: Permission.BLUETOOTH_ADVERTISE
  49. 📱 Hardware 푸시 알림 (FCM, APNs)을 멀티플랫폼 환경에서 수신 /

    핸들링 할 수 있는 라이브러리 디바이스의 현재 위치(실시간 포함)를 멀티플랫폼 환경에서 가져올 수 있는 라이브러리 디바이스의 생체 인증(지문, FaceID, TouchID)을 멀티플랫폼 환경에서 사용할 수 있게 해주는 라이브러리
  50. 📱 Hardware Bluetooth Low Enerey (BLE) 장치를 멀티플랫폼 환경에서 스캔하고

    통신하게 해주는 라이브러리 kable 멀티플랫폼 환경에서 실시간 네트워크 연결 상태를 핸들링 할 수 있는 라이브러리 multiplatform- connectivity-status
  51. 🏗 Architecture 라이브러리 자체 제공 라우팅 기능 + 플러그인 형태의

    UI를 통해 비즈니스 로직 (ex. Flutter의 BLoC)을 작성해주는 라이브러리 Orbit Multiplatform 멀티플랫폼 환경에서 MVI / Redux 같은 형태로 뷰모델을 작성할 수 있게 해주는 라이브러리 라이브러리 문서 상으로는 MVVM+ 라고 주장(?) 중
  52. 🏗 Architecture | SingleComponentExample.kt interface ListComponent { val model: Value<Model>

    fun onItemClicked(item: String) data class Model( val items: List<String>, ) }
  53. 🏗 Architecture | SingleComponentExample.kt class DefaultListComponent( componentContext: ComponentContext, private val

    onItemSelected: (item: String) -> Unit, ) : ListComponent { override val model: Value<ListComponent.Model> = MutableValue(Model(items = List(100) { "Item $it" })) override fun onItemClicked(item: String) { onItemSelected(item) } }
  54. 🏗 Architecture | SingleComponentExampleUI.kt @Composable fun ListContent(component: ListComponent, modifier: Modifier

    = Modifier) { val model by component.model.subscribeAsState() LazyColumn { items(items = model.items) { item -> Text( text = item, modifier = Modifier.clickable { component.onItemClicked(item = item) }, ) } } }
  55. 🏗 Architecture | SingleComponentExampleUI.swift struct DetailsView: View { private let

    list: ListComponent @StateValue private var model: ListComponentModel init(_ list: ListComponent) { self.list = list _model = StateValue(list.model) } var body: some View { List(model.items, ...) { item in // Display the item } } }
  56. 🏗 Architecture | NavigationComponentExample.kt interface RootComponent { val stack: Value<ChildStack<*,

    Child>> // iOS는 여러개의 화면을 동시에 pop 할 수 있기 때문에 특정 index 만큼 pop 할 수 있도록 처리 fun onBackClicked(toIndex: Int) // Child Navigation 모두 정의 sealed class Child { class ListChild(val component: ListComponent) : Child() class DetailsChild(val component: DetailsComponent) : Child() } }
  57. 🏗 Architecture | NavigationComponentExample.kt class DefaultRootComponent( componentContext: ComponentContext, ) :

    RootComponent, ComponentContext by componentContext { private val navigation = StackNavigation<Config>() // TODO: Navigation Definition @Serializable private sealed interface Config { @Serializable data object List : Config @Serializable data class Details(val item: String) : Config } }
  58. 🏗 Architecture | NavigationComponentExample.kt class DefaultRootComponent(...) : RootComponent, ComponentContext by

    componentContext { override val stack: Value<ChildStack<*, RootComponent.Child>> = childStack( source = navigation, serializer = Config.serializer(), initialConfiguration = Config.List, handleBackButton = true, childFactory = ::child, ) override fun onBackClicked(toIndex: Int) { navigation.popTo(index = toIndex) } }
  59. 🏗 Architecture | NavigationComponentExample.kt class DefaultRootComponent(...) : RootComponent, ComponentContext by

    componentContext { private fun listComponent(componentContext: ComponentContext): ListComponent = DefaultListComponent( componentContext = componentContext, onItemSelected = { item: String -> // Supply dependencies and callbacks navigation.push(Config.Details(item = item)) // Push the details component }, ) private fun detailsComponent(componentContext: ComponentContext, config: Config.Details): DetailsComponent = DefaultDetailsComponent( componentContext = componentContext, item = config.item, // Supply arguments from the configuration onFinished = navigation::pop, // Pop the details component ) }
  60. 🏗 Architecture | NavigationComponentExample.kt class DefaultRootComponent(...) : RootComponent, ComponentContext by

    componentContext { private fun child(config: Config, componentContext: ComponentContext): RootComponent.Child = when (config) { is Config.List -> ListChild(listComponent(componentContext)) is Config.Details -> DetailsChild(detailsComponent(componentContext, config)) } }
  61. 🏗 Architecture | NavigationComponentExampleUI.kt @Composable fun RootContent(component: RootComponent, modifier: Modifier

    = Modifier) { Children( stack = component.stack, modifier = modifier, animation = stackAnimation(fade() + scale()), ) { when (val child = it.instance) { is ListChild -> ListContent(component = child.component) is DetailsChild -> DetailsContent(component = child.component) } } }
  62. 🏗 Architecture | Reference: [Medium - Matthew Dolan] Top Android

    MVI libraries in 2021 https://appmattus.medium.com/top-android-mvi-libraries-in-2021-de1afe890f27
  63. 🏗 Architecture | 코틀린 코루틴 100% Lifecycle-Safety Flow Collection [Android]

    SavedStateHandle 대응 완료 테스트 API 지원 [Android] RxJava + LiveData 지원
  64. 🏗 Architecture | What’s Orbit? State + SideEffect -> Managed

    by ContainerHost Android ViewModel 안드로이드에서는 ViewModel이 Orbit의 ContainerHost 역할이 됨
  65. 🏗 Architecture | State.kt data class CalculatorState( val total: Int

    = 0 ) sealed class CalculatorSideEffect { data class Toast(val text: String) : CalculatorSideEffect() }
  66. 🏗 Architecture | CalculatorViewModel.kt class CalculatorViewModel: ContainerHost<CalculatorState, CalculatorSideEffect>, ViewModel() {

    override val container = container<CalculatorState, CalculatorSideEffect>(CalculatorState()) fun add(number: Int) = intent { postSideEffect(CalculatorSideEffect.Toast("${state.total}! + $number = ?")) reduce { state.copy(total = state.total + number) } } }
  67. 🏗 Architecture | CalculatorScreen.kt @Composable fun CalculatorScreen(viewModel: CalculatorViewModel) { val

    state by viewModel.collectAsState() viewModel.collectSideEffect { when(it) { CalculatorSideEffect.Toast -> Toast.makeText(...) } } Calculator( state = state ) { Text("${state.total}") } }
  68. 🏗 Architecture 멀티플랫폼 환경에서 MVVM(Model, View, ViewModel) 아키텍처 구성요소를 제공해주는

    라이브러리 번외.. 당연히 MVVM 멀티플랫폼 라이브러리도 있습니다.
  69. 🏗 Architecture 멀티플랫폼 환경에서 MVVM(Model, View, ViewModel) 아키텍처 구성요소를 제공해주는

    라이브러리 Android macOS iOS watchOS tvOS Windows Linux Web ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
  70. 💥 Crash Reporting CrashKiOS 오류 모니터링 플랫폼 ‘Sentry’의 Multiplatform SDK

    각 OS별 SDK도 지원하지만 Kotlin Multiplatform SDK도 공식 지원 Firebase Crashlytics와 Bugsnag를 지원하는 멀티플랫폼 크래시 리포트 라이브러리 Firebase Crashlytics를 지원하고, Fatal Error / Non-Fatal Error로 나눠 세부적인 크래시 로깅을 할 수 있는 라이브러리
  71. 💥 Crash Reporting Android macOS iOS watchOS tvOS Windows Linux

    Web ✅ ❌ ✅ ❌ ❌ ❌ ❌ ❌ CrashKiOS
  72. 🔐 Security 크로스플랫폼 Low Level 암호화 라이브러리인 libsodium 을 멀티플랫폼에

    Wrapping 해놓은 라이브러리 libsodium 함수를 바인딩 해놓은 것이라서 함수 호출해서 low-level API 직접 호출 가능 cryptography-kotlin kotlin-multiplatform -libsodium OpenSSL, WebCrypto 같은 암호화 라이브러리를 멀티플랫폼 환경에 Wrapping 해놓은 라이브러리 다양한 암호화 관련 함수와 Secure Random, SHA~, AES, RSA, ECDSA 등을 지원함
  73. 🔐 Security Android macOS iOS watchOS tvOS Windows Linux Web

    ✅ ✅ ✅ ❌ ❌ ❌ ✅ ✅ cryptography-kotlin
  74. 🔐 Security Android macOS iOS watchOS tvOS Windows Linux Web

    ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ kotlin-multiplatform -libsodium
  75. 📄 File (I/O) java.io 와 java. nio를 보완하여 데이터 접근,

    저장, 처리를 쉽게 해주는 멀티플랫폼 라이브러리 네트워킹 라이브러리인 OkHttp에도 사용중임 물론, Compression, Concurrency 등 JVM에서만 작동하는 API들도 존재하지만 Byte를 Hex로 바꾼다던가 (반대도 가능), 인코딩, 해싱, 파일 시스템 등 많은 편의 기능은 멀티플랫폼 환경에서도 작동함 okio
  76. 📄 File (I/O) okio Android macOS iOS watchOS tvOS Windows

    Linux Web ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
  77. 🗃 Serializer JVM, Android 환경 뿐만 아니라 코틀린 멀티플랫폼 환경에서도

    Json, Protocol Buffer 등의 포맷을 Serialization 해주는 라이브러리 Kotlin 언어 자체에 대한 특정 (ex. default value, nullable) 등을 모두 고려하여 설계됨 @Serializable 어노테이션이 있는 클래스만 직렬화 함 → 개발자 실수 등으로 인해 누락되거나 지원하지 않는 타입에 대해서는 컴파일 시점에서 오류를 발생시켜 버그를 사전에 방지할 수 있음 Reflection을 사용하지 않으며, kotlin compiler 플러그인 레벨에서 Serialization 관련 코드를 생성함 kotlinx.serialization
  78. 🗃 Serializer KSerializerExample.kt import kotlinx.serialization.* import kotlinx.serialization.json.* @Serializable data class

    Project(val name: String, val language: String) fun main() { val data = Project("kotlinx.serialization", "Kotlin") val string = Json.encodeToString(data) println(string) // {"name":"kotlinx.serialization","language":"Kotlin"} val obj = Json.decodeFromString<Project>(string) println(obj) // Project(name=kotlinx.serialization, language=Kotlin) }
  79. ⏰ Date / Time JVM, Android 환경 뿐만 아니라 코틀린

    멀티플랫폼 환경에서도 날짜, 시간, 시간대(TimeZone) 등을 쉽게 계산하고 표현할 수 있게 해주는 라이브러리 특정 시간에 시간을 더하기 / 빼기 등의 연산을 할 수 있고, 다양한 시간대로 변환도 할 수 있으며, 시간차이도 구할 수 있는 등 시간 관련하여 유용한 함수들이 제공됨 kotlinx.datetime
  80. ⏰ Date / Time ConvertExample.kt "2010-06-01T22:19:44.475Z".toInstant() "2010-06-01T22:19:44".toLocalDateTime() "2010-06-01".toLocalDate() "12:01:03".toLocalTime() "12:0:03.999".toLocalTime()

    TimeCalcExample.kt val now = Clock.System.now() val systemTZ = TimeZone.currentSystemDefault() val tomorrow = now.plus(2, DateTimeUnit.DAY, systemTZ) val threeYearsAndAMonthLater = now.plus(DateTimePeriod(years = 3, months = 1), systemTZ)
  81. ⏰ Date / Time kotlinx.datetime Android macOS iOS watchOS tvOS

    Windows Linux Web ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
  82. Firebase Android SDK + Firebase iOS SDK를 하나의 Kotlin Multiplatform

    환경에 Wrapping 해놓은 라이브러리 Firebase Firebase Kotlin SDK by GitLiveApp
  83. Firebase 비공식 라이브러리이기 때문에 아직 지원하지 않는 기능 + API도

    존재 하지만 메이저 한 기능은 사용할 수 있는 수준 하지만, Crashlytics, FCM은 아직 구현되지 않았기 때문에 다른 라이브러리 혹은 직접 구현이 필요함
  84. Android macOS iOS watchOS tvOS Windows Linux Web ✅ ❌

    ✅ ❌ ❌ ❌ ❌ ❌ Firebase Kotlin SDK by GitLiveApp Firebase
  85. KMP 개발을 위한 알아두면 좋은 라이브러리 소개 / DI 프레임워크

    찍먹하기 Incheon/Songdo 이상훈 | at Studio Product Engineer