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

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

Dora Lee
December 11, 2023

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

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

Dora Lee

December 11, 2023
Tweet

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