본문 바로가기
카테고리 없음

Kotlin 다중 플랫폼 — 하나의 코드 기반에서 Kotlin을 사용하여 Android, 데스크톱 및 웹용 앱을 만드는 방법

by Emily Blunt 2022. 4. 21.
반응형

Kotlin 다중 플랫폼 — 하나의 코드 기반에서 Kotlin을 사용하여 Android, 데스크톱 및 웹용 앱을 만드는 방법

 

JetBrains의 Compose Multiplatform은 작년에 베타 버전에 도달하여 크로스 플랫폼 UI 프레임워크를 제공하려는 야망을 더욱 강화했습니다.

Kotlin 다중 플랫폼 전체 원칙(출처: kotlinlang.org)

Compose Multiplatform이 아직 무엇인지 모르는 경우 JetBrains 웹 사이트에서 직접 복사한 TLDR이 있습니다.

Google의 최신 툴킷을 기반으로 하고 JetBrains에서 제공하는 Kotlin용 빠른 반응형 데스크톱 및 웹 UI 프레임워크입니다.

Jetpack Compose와 결합하면 이제 Kotlin만 사용하여 모바일, 데스크톱 및 웹을 대상으로 하는 앱을 만들 수 있습니다.

Flutter와 비슷하지만 Kotlin을 사용한다는 말씀인가요?

Kotlin Multiplatform(KMP)과 Flutter 사이에는 전체 Dart 대 Kotlin 문제 외에도 상당히 눈에 띄는 차이점이 있습니다. 내가 가장 흥미롭게 생각하는 것은 플랫폼 간 아키텍처를 처리하는 방법입니다.

Flutter에서는 비즈니스 로직과 UI를 작성하고 어디서나 실행할 수 있습니다. UI를 플랫폼에 적용하기 위해 일부 적응 로직을 작성할 수는 있지만 결국 모바일, 데스크톱 또는 웹 앱에 빌드하든 관계없이 앱은 Flutter 엔진을 통해 렌더링됩니다(Skia 그래픽 기반 도서관). 이것은 이식성이 뛰어나지만 기본 UI 경험을 쉽게 제공하지 못합니다.

KMP에서는 비즈니스 로직을 한 번 작성하고 공유 라이브러리에 넣은 다음 Android용 Jetpack Compose, iOS용 Swift, 데스크톱용 Compose Multiplatform 등과 같이 대상으로 하는 각 플랫폼의 기본 라이브러리를 사용하여 UI를 작성합니다. 따라서 앱은 UI에 대한 플랫폼 적응의 비용으로 더 네이티브 느낌을 갖게 됩니다.

결론은 이 두 가지 접근 방식이 서로 다른 접근 방식을 제공하며 어떤 접근 방식을 선택하느냐가 사용 사례에 따라 다르다는 것입니다.

사실, KMP는 훌륭하게 들립니다. 저를 등록하십시오!

글쎄, 그것을 실험하는 데 상당한 시간을 보낸 후에, 나는 당신의 열정을 이해하고 공유합니다! 그러나 지금은 이론을 제쳐두고 "어떻게"에 집중합시다.

글쎄요, 우리는 무엇을 개발해야 할지 결정해야 합니다. 그래야 제가 그 모든 것을 실제로 보여줄 수 있습니다. Flutter "Hello World" 앱에서 영감을 받아 사용자가 숫자 값을 증가 및 감소하고 가장 최근에 등록된 작업을 기록할 수 있는 카운터 앱을 만들어 보겠습니다.

충분히 간단해 보입니다!

그렇다면 지금 우리는 어떤 아키텍처를 사용하고 있습니까?

건축물

GUI 애플리케이션(특히 Android에서)을 구축하기 위한 일반적인 솔루션인 MVVM(Model-View-ViewModel)과 함께 Clean Architecture를 사용합니다. 튜토리얼의 나머지 부분에서는 사용자가 이에 대해 잘 알고 있다고 가정합니다. 그렇지 않은 경우 이 페이지를 떠나 전에 읽을 수 있습니다. 네, 허락하셨습니다! :)

좋아, 그럼 시작해 보자. 이것이 우리가 앱을 구성하는 방법입니다:

  • 도메인: 여기에는 앱에 대한 모델이 포함됩니다. 이 경우에는 단순히 카운터에 대한 데이터 클래스(값 및 마지막 작업 메시지)
  • 데이터: 이 카운터를 저장할 추상 데이터 소스
  • 사용 사례: 앱의 모든 사용 사례 클래스, 즉: 카운터 증가, 감소 및 값 가져오기
  • 프레젠테이션: 이 카운터 뷰의 뷰 모델
  • 프레임워크: 데이터 소스 구현 및 플랫폼별 UI

위의 모든 요소는 "프레임워크"의 일부인 UI를 제외한 모든 플랫폼에서 공유할 수 있습니다.

우리의 다른 모듈

자, 이제 코드를 파헤쳐 보겠습니다. IntelliJ IDEA Community를 사용하고 있으므로 이를 염두에 두십시오. 하지만 여러분이 가장 좋아하는 IDE에 적응할 때마다 여러분을 신뢰합니다.

위에서 설명한 대로 프로젝트 구조로 새 프로젝트를 만드는 것으로 시작한 다음 모든 플랫폼을 다룰 때까지 각 클래스를 차례로 만들어야 합니다. IDEA는 우리가 사용할 수 있는 KMP 응용 프로그램용으로 미리 만들어진 템플릿을 제공하지만 학습 목적으로 처음부터 만들어 보겠습니다.

IDEA를 연 다음 "파일 > 새 프로젝트"로 이동합니다. "Gradle"을 선택한 다음 오른쪽 패널에서 "Kotlin DSL 빌드 스크립트"를 제외한 모든 항목을 비워 둡니다. 그런 다음 Gradle 대신 Kotlin DSL을 빌드 스크립트로 가져옵니다.

새 프로젝트를 만들고 이름을 "KmpDemo"로 지정하겠습니다.

그런 다음 프로젝트의 이름을 지정하고 만듭니다.

모듈

첫 번째 단계는 다른 모듈을 만드는 것입니다. common, android, desktop그리고 web. 우리는 시작합니다 common 다른 모든 모듈이 그것에 의존하기 때문입니다.

프로젝트 루트를 마우스 오른쪽 버튼으로 클릭한 다음 "새로 만들기 > 모듈..."을 클릭합니다. 익숙한 대화 상자가 표시됩니다. 이번에는 다른 항목과 함께 "Kotlin/Multiplatform"을 확인하십시오.

공통용 새 모듈

이름을 "공통"으로 지정하십시오. 생성 후 빌드 동기화 오류가 발생합니다.

'공통(:공통)'에서 하나 이상의 Kotlin 대상을 초기화하세요.

지금은 무시하십시오. 우리는 나중에 그것을 고칠 것입니다.

동일한 방법을 사용하여 생성 desktop, android 그리고 web 모듈.

루트 아래에 새 파일을 만들고 이름을 지정하십시오. gradle.properties 다음 내용으로:

kotlin.code.style=official
android.useAndroidX=true
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
android.enableJetifier=true

이제 프로젝트 구조가 다음과 같아야 합니다.

당사의 최상위 모듈

프로젝트 구조를 마무리하기 위해 아직 해야 할 일이 있습니다. 소스 세트를 만들어야 합니다.

소스 세트

KMP 프로젝트는 일반적으로 대상 특성(공통, JVM 및 JS)에 따라 소스 파일을 서로 다른 세트로 구성합니다. 공식 문서에 자세히 설명된 권장 구조를 따릅니다.

아래에 다음 디렉토리를 작성하십시오. common:

  • src/androidMain/kotlin
  • src/androidTest/kotlin
  • src/commonMain/kotlin
  • src/commonTest/kotlin
  • src/desktopMain/kotlin
  • src/desktopTest/kotlin

아래에 desktop:

  • src/jvmMain/kotlin
  • src/jvmTest/kotlin

아래에 web:

  • src/jsMain/kotlin
  • src/jsTest/kotlin

그리고 아래 android:

잠깐만, 공통 모듈이 플랫폼에 독립적일 거라고 생각했나요? 왜 우리는 이것들을 가지고 있습니까? desktopMain 그리고 androidMain 거기 물건?

좋은 질문!

이 (가상의) 사용 사례를 고려해 보겠습니다. 다중 플랫폼 앱을 개발 중이지만 일부 플랫폼별 API에도 액세스해야 합니다. 이것은 일부 OS 버전을 검색하거나 일부 로깅 시스템에 연결하거나 임의의 UUID를 생성할 수 있습니다.

일반적으로 Dependency Inversion을 활용하여 공통 모듈에서 인터페이스를 생성하고 플랫폼별 구현에서 인터페이스를 생성합니다. 그러나 UUID 생성과 같이 기능이 매우 단순한 경우 일부 상용구 코드가 발생할 수 있습니다.

KMP는 다음을 사용하여 이를 달성하는 더 간단한 방법을 허용합니다. 예상/실제 위의 메커니즘과 구조. 따라서 다음과 같이 할 수 있습니다.

// Under Common
expect fun randomUUID(): String// Under Android
import java.util.*
actual fun randomUUID() = UUID.randomUUID().toString()  
// And so on for all other platforms

이것은 카운터 앱에 사용되지 않지만 미래에 대비하는 것이 좋습니다. :)

앱으로 돌아가면 이제 다음 구조가 있어야 합니다.

멋진 색상이 곧 나타날 테니 걱정하지 마세요.

빌드 스크립트를 구현할 시간입니다!

스크립트 빌드

각 모듈과 루트에는 다음과 같은 빌드 스크립트가 포함되어 있습니다. build.gradle.kts그리고 때때로 이름이 지정된 설정 settings.gradle.kts. 이 파일은 모듈, 종속성, 프레임워크 등 프로젝트가 빌드되는 방식을 정의합니다.

settings.gradle.kts쓰다:

이것은 단순히 외부 종속성과 프로젝트 모듈을 가져오는 데 사용할 리포지토리를 정의합니다.

build.gradle.kts쓰다:

더 많이 동일하지만 이번에는 빌드 스크립트와 모든 모듈에 대해 수행합니다.

common/build.gradle.kts쓰다:

꽤 많은 것들이 여기에 있습니다. 이것을 풀어보자.

먼저, plugins 모듈을 지정하는 정의는 Compose 모듈과 Android 라이브러리입니다. 그런 다음 몇 가지 옵션을 사용하여 JS 및 JVM 대상을 지정합니다. 이것은 다른 모듈이 common을 가져올 수 있도록 하는 데 필요합니다. 옵션은 기본 옵션입니다.

그런 다음 소스 세트, 즉 Kotlin 소스 파일이 있는 위치와 유형 및 종속성을 정의합니다. 참고 commonMain 에 달려있다 kotlinx-coroutines-core 그리고 kodein-di-framework-compose. 마지막으로 Android 대상 옵션을 이전에 Android로 작업한 적이 있는 경우 친숙한 방식으로 정의합니다.

보시다시피 Android용 매니페스트를 정의하므로 언급된 경로에 추가해 보겠습니다. common/src/androidMain/AndroidManifest.xml:

이제 우리가 돌보자 desktop/build.gradle.kts:

더 많이 동일하지만 명시적으로 종속성을 정의합니다. common.

웹타임! 쓰다 web/build.gradle.kts:

더 많이 동일하지만 다시 한 번 우리는 common에 대한 종속성을 명시적으로 정의합니다.

그리고 지금을 위해 android/build.gradle.kts:

빌드 스크립트가 끝났으니 이제 애플리케이션 소스 코드로 넘어갑시다!

공통 소스 코드

아래의 commonMain/kotlin 디렉토리, 패키지 생성 org.example.kmpdemo.

이 패키지에서 파일이 있는 도메인 패키지를 만듭니다. Counter.kt:

package org.example.kmpdemo.domain
/** * Model for the number counter * @param value The counter value * @param message Additional context attached to the counter */ data class Counter ( val value: Int = 0, val message: String = "Init" )  

이것은 우리 카운터의 모델입니다. 충분히 간단합니다!

지금에 org.example.kmpdemo생성 data 파일과 함께 패키지:

CounterDataSource.kt

CounterRepository.kt

흐름을 사용하여 카운터 값을 관찰하고 증가 및 감소 방법은 다음과 같이 정의됩니다. suspend 동기화 유연성을 위해.

이제 사용 사례를 살펴보겠습니다. 에 org.example.kmpdemo생성 usecase 파일과 함께 패키지:

GetCounter.kt

DecrementCounter.kt

IncrementCounter.kt

이제 뷰 모델을 처리해 보겠습니다. 에 org.example.kmpdemo파일로 프레젠테이션 패키지 생성 CounterViewModel.kt:

증가 및 감소 연산은 코루틴에서 구현됩니다.

거의 다 왔어! 에 org.example.kmpdemo파일로 프레임워크 패키지 생성 InMemoryCounterDataSource.kt:

이것은 데이터 소스의 실제(기본) 구현이며 카운터는 이 클래스에 저장된 싱글톤입니다. 즉, 앱이 다시 시작될 때마다 값이 재설정되지만 지금은 충분합니다!

의존성 주입(DI)을 위한 시간. 만들기 file di/ServicesModule.kt 아래에 org.example.kmpdemo.

이는 이제 모든 플랫폼의 앱 보기에 보기 모델을 주입할 수 있음을 의미합니다. 정돈된!

우리는 이제 끝났습니다 common 기준 치수!

데스크탑 소스 코드

데스크톱 앱을 구현해 보겠습니다. 나는 이것을 Linux에서만 테스트했지만 이것은 Windows와 Mac에서 같은 방식으로 작동해야 합니다. 파일을 만들고, desktop/src/jvmMain/kotlin/Main.kt:

우리의 의존성 덕분에 common; DI 모듈을 검색하고 뷰 모델을 앱에 주입하기만 하면 됩니다. 카운터 Flow는 Compose와 잘 작동하는 State로 변환되고 나머지는 매우 간단합니다.

메인 기능 옆에 있는 실행 아이콘을 클릭하면 다음과 같은 보상이 주어집니다.

그리고 버튼을 클릭하면 작동합니다.

패키지 만들기 org.example.kmpdemo.android ~와 함께 MainActivity.kt 그 안에:

친숙한 느낌? 실행 시간:

안드로이드: 확인!

웹 소스 코드

먼저 기반을 만들어야 합니다. index.html 파일, 에 web/src/jsMain/resources/index.html:

 

이제 생성 web/src/jsMain/kotlin/Main.kt 파일:

메인 기능 옆에 있는 실행 아이콘을 클릭하면 다음과 같은 보상이 주어집니다.

웹 앱. 보시다시피 CSS는 제 열정입니다...

잠깐, 이것은 매우 다르게 보입니다. 또한 div, H1… 내가 HTML을 작성하고 있습니까?!

Compose for Web은 불행히도 다음과 같은 점에서 다른 플랫폼에 약간 뒤쳐져 있습니다.

"웹용 Compose는 현재 기존 위젯을 직접 재사용하는 것을 허용하지 않습니다(기본적으로 대부분의 위젯에 대한 직접 코드 공유를 허용하는 Jetpack Compose의 Android 및 Desktop 대상과 달리)." 원천

그러나 DOM API는 기능적이며 생성된 HTML 및 CSS와 관련하여 상당히 예측할 수 있다는 이점이 있습니다.

끝까지 팔로우해주셔서 감사합니다!

이 작은 예가 KMP에 대한 추가 통찰력과 이러한 맥락에서 KMP를 시도하려는 의지를 제공하기를 바랍니다. 이 프로젝트의 전체 코드는 이 GitHub 리포지토리에서 사용할 수 있습니다. 자유롭게 실험해보고 질문과 피드백을 보내주세요!

반응형

댓글