Android 애플리케이션을 iOS에서 동작하게 만들기 – 튜토리얼
이 튜토리얼에서는 기존 Android 애플리케이션을 크로스 플랫폼으로 만들어 Android와 iOS 모두에서 작동하도록 하는 방법을 보여줍니다. Android와 iOS용 코드를 한 곳에서 동시에 작성할 수 있게 됩니다.
이 튜토리얼은 사용자 이름과 비밀번호를 입력하는 단일 화면이 있는 샘플 Android 애플리케이션을 사용합니다. 자격 증명은 유효성 검사를 거쳐 인메모리 데이터베이스에 저장됩니다.
애플리케이션이 iOS와 Android 모두에서 작동하도록 하려면, 먼저 코드 일부를 공유 모듈(shared module)로 이동하여 코드를 크로스 플랫폼으로 만들어야 합니다. 그 후 Android 애플리케이션에서 크로스 플랫폼 코드를 사용하고, 새로운 iOS 애플리케이션에서도 동일한 코드를 사용하게 됩니다.
Kotlin 멀티플랫폼이 익숙하지 않다면, 먼저 처음부터 크로스 플랫폼 애플리케이션 만들기를 통해 기본 개념을 익히는 것이 좋습니다.
개발 환경 준비하기
퀵스타트 가이드의 지침에 따라 Kotlin 멀티플랫폼 개발을 위한 환경 설정을 완료하세요.
iOS 애플리케이션 실행 등 이 튜토리얼의 특정 단계를 완료하려면 Apple의 요구 사항에 따라 macOS가 설치된 Mac이 필요합니다.
Android Studio에서 버전 제어(Version Control)를 통해 새 프로젝트를 생성합니다.
texthttps://github.com/Kotlin/kmp-integration-samplemaster브랜치에는 단순한 Android 애플리케이션인 프로젝트의 초기 상태가 포함되어 있습니다. iOS 애플리케이션과 공유 모듈이 포함된 최종 상태를 보려면final브랜치로 전환하세요.Project 뷰로 전환합니다.

코드를 크로스 플랫폼으로 만들기
코드를 크로스 플랫폼으로 만들기 위해 다음 단계를 따릅니다.
- 크로스 플랫폼으로 만들 코드 결정하기
- 크로스 플랫폼 코드를 위한 공유 모듈 생성하기
- 코드 공유 테스트하기
- Android 애플리케이션에 공유 모듈 의존성 추가하기
- 비즈니스 로직을 크로스 플랫폼으로 만들기
- Android에서 크로스 플랫폼 애플리케이션 실행하기
크로스 플랫폼으로 만들 코드 결정하기
Android 애플리케이션의 코드 중 어떤 코드를 iOS와 공유하고 어떤 코드를 네이티브로 유지할지 결정합니다. 간단한 규칙은 다음과 같습니다: 가능한 한 많이 재사용하고 싶은 부분을 공유하세요. 비즈니스 로직은 Android와 iOS 모두에서 동일한 경우가 많으므로 재사용하기에 아주 좋은 후보입니다.
샘플 Android 애플리케이션에서 비즈니스 로직은 com.jetbrains.simplelogin.androidapp.data 패키지에 저장되어 있습니다. 향후 만들 iOS 애플리케이션에서도 동일한 로직을 사용할 것이므로, 이 부분을 크로스 플랫폼으로 만들어야 합니다.

크로스 플랫폼 코드를 위한 공유 모듈 생성하기
iOS와 Android 모두에서 사용되는 크로스 플랫폼 코드는 공유 모듈에 저장됩니다. Android Studio와 IntelliJ IDEA는 모두 Kotlin 멀티플랫폼을 위한 공유 모듈 생성 마법사(wizard)를 제공합니다.
기존 Android 애플리케이션과 향후 생성할 iOS 애플리케이션을 모두 연결할 공유 모듈을 생성합니다.
Android Studio의 메인 메뉴에서 File | New | New Module을 선택합니다.
템플릿 목록에서 Kotlin Multiplatform Shared Module을 선택합니다. 모듈 이름은
shared로 두고 패키지 이름을 입력합니다.textcom.jetbrains.simplelogin.sharedFinish를 클릭합니다. 마법사가 공유 모듈을 생성하고, 빌드 스크립트를 적절하게 변경한 후 Gradle 동기화(sync)를 시작합니다.
동기화가 완료될 때까지 기다립니다.
shared디렉터리에서 다음과 같은 파일 구조를 볼 수 있습니다.
결과 프로젝트의 레이아웃을 더 잘 이해하고 싶다면, Kotlin 멀티플랫폼 프로젝트 구조의 기초를 참조하세요.
shared모듈은 Android 애플리케이션의 라이브러리로 사용될 것이므로,shared/build.gradle.kts파일의kotlin.android {}블록을 다음androidLibrary {}블록으로 교체합니다.kotlinimport org.jetbrains.kotlin.gradle.dsl.JvmTarget kotlin { androidLibrary { namespace = "com.jetbrains.simplelogin.shared" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { jvmTarget = JvmTarget.JVM_11 } androidResources { enable = true } withHostTestBuilder { } withDeviceTestBuilder { sourceSetTreeName = "test" }.configure { instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } } //... }
공유 모듈에 코드 추가하기
이제 공유 모듈이 생겼으므로, shared/src/commonMain/kotlin/com.jetbrains.simplelogin.shared 디렉터리에 공유할 공통 코드를 추가합니다.
다음 코드로 새로운
Greeting클래스를 생성합니다.kotlinpackage com.jetbrains.simplelogin.shared class Greeting { private val platform = getPlatform() fun greet(): String { return "Hello, ${platform.name}!" } }기존 파일의 코드를 다음과 같이 교체합니다.
commonMain/Platform.kt:kotlinpackage com.jetbrains.simplelogin.shared interface Platform { val name: String } expect fun getPlatform(): PlatformandroidMain/Platform.android.kt:kotlinpackage com.jetbrains.simplelogin.shared import android.os.Build class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" } actual fun getPlatform(): Platform = AndroidPlatform()iosMain/Platform.ios.kt:kotlinpackage com.jetbrains.simplelogin.shared import platform.UIKit.UIDevice class IOSPlatform: Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion } actual fun getPlatform(): Platform = IOSPlatform()
이제 플랫폼 이름 속성을 가진 플랫폼별 객체를 반환하는 공통 getPlatform() 함수가 준비되었습니다.
Android 애플리케이션에 공유 모듈 의존성 추가하기
Android 애플리케이션에서 크로스 플랫폼 코드를 사용하려면, 공유 모듈을 애플리케이션에 연결하고 비즈니스 로직 코드를 그곳으로 이동시킨 후 해당 코드를 크로스 플랫폼으로 만들어야 합니다.
app/build.gradle.kts파일에 공유 모듈에 대한 의존성을 추가합니다.kotlindependencies { // ... implementation(project(":shared")) }IDE에서 제안하는 대로 또는 File | Sync Project with Gradle Files 메뉴 항목을 사용하여 Gradle 파일을 동기화합니다.
app/src/main/java/디렉터리의com.jetbrains.simplelogin.androidapp.ui.login패키지에 있는LoginActivity.kt파일을 엽니다.공유 모듈이 애플리케이션에 성공적으로 연결되었는지 확인하기 위해,
onCreate()메서드에Log.i()호출을 추가하여greet()함수의 결과를 로그에 출력합니다.kotlinoverride fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) Log.i("Login Activity", "Hello from shared module: " + (Greeting().greet())) // ... }IDE의 제안에 따라 누락된 클래스를 임포트합니다.
툴바의 실행 구성 드롭다운 옆에 있는 디버그 아이콘을 클릭합니다.

Logcat 도구 창에서 로그에 "Hello"를 검색하면 공유 모듈로부터의 인사말을 찾을 수 있습니다.

비즈니스 로직을 크로스 플랫폼으로 만들기
이제 비즈니스 로직 코드를 Kotlin 멀티플랫폼 공유 모듈의 commonMain 소스 세트로 추출할 수 있습니다. 이를 통해 Android와 iOS 모두에서 코드를 사용할 수 있게 됩니다.
비즈니스 로직 코드인
com.jetbrains.simplelogin.androidapp.data를app디렉터리에서shared/src/commonMain디렉터리의com.jetbrains.simplelogin.shared패키지로 이동합니다.
Android Studio에서 수행할 작업을 묻는 메시지가 나타나면 패키지를 이동하도록 선택하고 리팩터링을 승인합니다.

플랫폼 종속 코드에 대한 모든 경고를 무시하고 Refactor Anyway를 클릭합니다.

Android 전용 코드를 크로스 플랫폼 Kotlin 코드로 교체하거나, expected 및 actual 선언을 사용하여 Android 전용 API에 연결함으로써 이를 제거합니다. 자세한 내용은 다음 섹션을 참조하세요.
Android 전용 코드를 크로스 플랫폼 코드로 교체
코드가 Android와 iOS 모두에서 잘 작동하도록 하려면, 이동된
data디렉터리 내의 모든 JVM 의존성을 가능한 한 Kotlin 의존성으로 교체합니다.LoginDataValidator클래스에서android.utils패키지의Patterns클래스를 이메일 유효성 검사 패턴과 일치하는 Kotlin 정규식으로 교체합니다.kotlin// 수정 전 private fun isEmailValid(email: String) = Patterns.EMAIL_ADDRESS.matcher(email).matches()kotlin// 수정 후 private fun isEmailValid(email: String) = emailRegex.matches(email) companion object { private val emailRegex = ("[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + "\\@" + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + "(" + "\\." + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + ")+").toRegex() }Patterns클래스에 대한 임포트 문을 제거합니다.kotlinimport android.util.PatternsLoginDataSource클래스에서login()함수의IOException을RuntimeException으로 교체합니다.IOException은 Kotlin/JVM 외부에서는 사용할 수 없습니다.```kotlin // 수정 전 return Result.Error(IOException("Error logging in", e)) ``` ```kotlin // 수정 후 return Result.Error(RuntimeException("Error logging in", e)) ```IOException에 대한 임포트 문도 제거합니다.kotlinimport java.io.IOException
플랫폼별 UUID 생성 구현
LoginDataSource클래스에서fakeUser를 위한 범용 고유 식별자(UUID)는 iOS에서 사용할 수 없는java.util.UUID클래스를 사용하여 생성됩니다.kotlinval fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")Kotlin 표준 라이브러리에서 UUID 생성을 위한 클래스를 제공하지만, 이 연습을 위해 플랫폼별 기능을 사용해 보겠습니다.
공통 코드에서
randomUUID()함수에 대한expect선언을 제공하고, 해당 소스 세트의 각 플랫폼(Android 및 iOS)에 대한actual구현을 제공합니다. 플랫폼별 API 연결에 대해 자세히 알아볼 수 있습니다.login()함수의java.util.UUID.randomUUID()호출을 각 플랫폼별로 구현할randomUUID()호출로 변경합니다.kotlinval fakeUser = LoggedInUser(randomUUID(), "Jane Doe")shared/src/commonMain디렉터리의com.jetbrains.simplelogin.shared패키지에Utils.kt파일을 생성하고expect선언을 추가합니다.kotlinpackage com.jetbrains.simplelogin.shared expect fun randomUUID(): Stringshared/src/androidMain디렉터리의com.jetbrains.simplelogin.shared패키지에Utils.android.kt파일을 생성하고 Android용randomUUID()의actual구현을 추가합니다.kotlinpackage com.jetbrains.simplelogin.shared import java.util.* actual fun randomUUID() = UUID.randomUUID().toString()shared/src/iosMain디렉터리의com.jetbrains.simplelogin.shared패키지에Utils.ios.kt파일을 생성하고 iOS용randomUUID()의actual구현을 추가합니다.kotlinpackage com.jetbrains.simplelogin.shared import platform.Foundation.NSUUID actual fun randomUUID(): String = NSUUID().UUIDString()shared/src/commonMain디렉터리의LoginDataSource.kt파일에서randomUUID함수를 임포트합니다.kotlinimport com.jetbrains.simplelogin.shared.randomUUID
이제 Kotlin은 Android와 iOS에서 각 플랫폼별 UUID 구현을 사용하게 됩니다.
Android에서 크로스 플랫폼 애플리케이션 실행하기
app 실행 구성을 실행하여 Android 앱이 이전처럼 잘 작동하는지 확인합니다.

크로스 플랫폼 애플리케이션을 iOS에서 동작하게 만들기
Android 애플리케이션을 크로스 플랫폼으로 만들었으므로, 이제 iOS 애플리케이션을 생성하고 그 안에서 공유 비즈니스 로직을 재사용할 수 있습니다.
- Xcode에서 iOS 프로젝트 생성하기
- KMP 프레임워크를 사용하도록 iOS 프로젝트 구성하기
- Android Studio에서 iOS 실행 구성 설정하기
- iOS 프로젝트에서 공유 모듈 사용하기
Xcode에서 iOS 프로젝트 생성하기
Xcode에서 File | New | Project를 클릭합니다.
대화 상자에서 iOS 탭으로 전환합니다.

App 템플릿을 선택하고 Next를 클릭합니다.
제품 이름(Product Name)에 "simpleLoginIOS"를 지정하고 Next를 클릭합니다.

프로젝트 위치로 크로스 플랫폼 애플리케이션이 저장된 디렉터리(예:
kmp-integration-sample)를 선택합니다.Android Studio에서는 다음과 같은 구조를 갖게 됩니다.

크로스 플랫폼 프로젝트의 다른 최상위 디렉터리와 일관성을 유지하기 위해 Xcode를 닫고
simpleLoginIOS디렉터리의 이름을iosApp으로 변경합니다.Xcode가 열려 있는 상태에서 폴더 이름을 변경하면 경고가 발생하고 프로젝트가 손상될 수 있습니다.

KMP 프레임워크를 사용하도록 iOS 프로젝트 구성하기
iOS 앱과 Kotlin 멀티플랫폼에 의해 빌드된 프레임워크 간의 통합을 직접 설정할 수 있습니다.
이 방법의 대안(SwiftPM 및 CocoaPods)은 iOS 통합 방법 개요에서 다루고 있습니다.
Android Studio에서
iosApp/simpleLoginIOS.xcodeproj디렉터리를 우클릭하고 Open In | Open In Associated Application을 선택하여 Xcode에서 iOS 프로젝트를 엽니다.Xcode의 Project 네비게이터에서 프로젝트 이름을 클릭하여 iOS 프로젝트 설정을 엽니다.
왼쪽의 Targets 섹션에서 simpleLoginIOS를 선택한 다음 Build Phases 탭을 클릭합니다.
+ 아이콘을 클릭하고 New Run Script Phase를 선택합니다.

Run Script 필드에 다음 스크립트를 붙여넣습니다.
bashif [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" exit 0 fi cd "$SRCROOT/.." ./gradlew :shared:embedAndSignAppleFrameworkForXcodeBased on dependency analysis 옵션을 비활성화합니다. 이렇게 하면 Xcode가 빌드할 때마다 스크립트를 실행하고, 출력 의존성 누락에 대해 매번 경고하지 않도록 보장합니다.

Run Script 단계를 위로 이동하여 Compile Sources 단계 앞에 배치합니다.

Build Settings 탭의 Build Options 아래에서 User Script Sandboxing 옵션을 비활성화합니다.

기본값인
Debug또는Release와 다른 커스텀 빌드 구성을 사용하는 경우, Build Settings 탭의 User-Defined 아래에KOTLIN_FRAMEWORK_BUILD_TYPE설정을 추가하고Debug또는Release로 설정하세요.Info 탭에서 커스텀
CADisableMinimumFrameDurationOnPhone속성을 추가하고YES로 설정하여 iOS에서 높은 화면 재생률(high refresh rate)을 활성화합니다.Signing & Capabilities 탭에서 개발 팀(development team)을 선택하거나 아직 없다면 생성합니다. 이를 통해 KMP 모듈에서 생성된
shared프레임워크를 서명할 수 있습니다.여기에서 Bundle Identifier가 고유한 값으로 설정되어 있는지 확인해야 합니다. 그렇지 않으면 Xcode 빌드가 실패할 수 있습니다.
Xcode에서 프로젝트를 빌드합니다(메인 메뉴의 Product | Build). 모든 설정이 올바르면 프로젝트가 성공적으로 빌드되어야 합니다 ("build phase will be run during every build" 경고는 무시해도 됩니다).
User Script Sandboxing 옵션을 비활성화하기 전에 프로젝트를 빌드했다면 빌드가 실패할 수 있습니다. Gradle 데몬 프로세스가 샌드박스에 갇혀 있을 수 있으므로 재시작해야 합니다. 프로젝트 디렉터리(이 예제에서는
kmp-integration-sample)에서 다음 명령을 실행하여 프로세스를 중지한 후 다시 빌드하세요.shell./gradlew --stop
Android Studio에서 iOS 실행 구성 설정하기
Xcode 설정이 올바른지 확인했다면 Android Studio로 돌아갑니다.
메인 메뉴에서 File | Sync Project with Gradle Files를 선택합니다. Android Studio가 자동으로 simpleLoginIOS라는 실행 구성을 생성합니다.
Android Studio는 자동으로 simpleLoginIOS라는 실행 구성을 생성하고
iosApp디렉터리를 연결된 Xcode 프로젝트로 표시합니다.실행 구성 목록에서 simpleLoginIOS를 선택합니다. iOS 에뮬레이터를 선택한 다음 Run을 클릭하여 iOS 실행 구성이 올바르게 작동하는지 확인합니다.

iOS 프로젝트에서 공유 모듈 사용하기
shared/build.gradle.kts 파일은 각 iOS 타겟에 대한 binaries.framework.baseName 속성을 sharedKit으로 정의합니다. 이것이 iOS 앱에서 사용할 수 있도록 Kotlin 멀티플랫폼이 빌드하는 프레임워크의 이름입니다.
통합을 테스트하기 위해 Swift 코드에서 공통 코드를 호출해 보겠습니다.
Android Studio에서
iosApp/simpleloginIOS/ContentView.swift파일을 열고 프레임워크를 임포트합니다.swiftimport sharedKit제대로 연결되었는지 확인하기 위해,
shared모듈의greet()함수를 사용하도록ContentView구조체의 코드를 업데이트합니다.swiftstruct ContentView: View { var body: some View { Text(Greeting().greet()) .padding() } }Android Studio의 iOS 실행 구성을 사용하여 앱을 실행하고 결과를 확인합니다.

ContentView.swift파일의 코드를 다시 업데이트하여 공유 모듈의 비즈니스 로직을 사용해 애플리케이션 UI를 렌더링하도록 합니다.kotlinsimpleLoginIOSApp.swift파일에서sharedKit모듈을 임포트하고ContentView()함수의 인수를 지정합니다.swiftimport SwiftUI import sharedKit @main struct SimpleLoginIOSApp: App { var body: some Scene { WindowGroup { ContentView(viewModel: .init(loginRepository: LoginRepository(dataSource: LoginDataSource()), loginValidator: LoginDataValidator())) } } }iOS 실행 구성을 다시 실행하여 iOS 앱에 로그인 양식이 나타나는지 확인합니다.
사용자 이름으로 "Jane"을, 비밀번호로 "password"를 입력합니다.
앞서 통합을 설정했으므로, iOS 앱은 공통 코드를 사용하여 입력을 검증합니다.

결과 확인 – 로직을 한 번만 업데이트하세요
이제 애플리케이션이 크로스 플랫폼이 되었습니다. shared 모듈에서 비즈니스 로직을 업데이트하면 Android와 iOS 모두에서 결과를 볼 수 있습니다.
사용자 비밀번호에 대한 유효성 검사 로직을 변경합니다. "password"는 유효한 옵션이 아니어야 합니다. 이를 위해
LoginDataValidator클래스의checkPassword()함수를 업데이트합니다 (빨리 찾으려면 를 두 번 누르고 클래스 이름을 붙여넣은 다음 Classes 탭으로 전환하세요).kotlinpackage com.jetbrains.simplelogin.shared.data class LoginDataValidator { //... fun checkPassword(password: String): Result { return when { password.length < 5 -> Result.Error("Password must be >5 characters") password.lowercase() == "password" -> Result.Error("Password shouldn't be \"password\"") else -> Result.Success } } //... }Android Studio에서 iOS와 Android 애플리케이션을 모두 실행하여 변경 사항을 확인합니다 (iOS 오류 메시지는 빨간색 경고 삼각형을 누르면 나타납니다).

이 튜토리얼의 최종 코드를 검토할 수 있습니다.
그 외 무엇을 공유할 수 있을까요?
애플리케이션의 비즈니스 로직을 공유했지만, 애플리케이션의 다른 레이어도 공유하도록 결정할 수 있습니다. 예를 들어, ViewModel 클래스 코드는 Android와 iOS 애플리케이션에서 거의 동일하며, 모바일 애플리케이션이 동일한 프레젠테이션 레이어를 가져야 한다면 이를 공유할 수 있습니다.
다음 단계는?
Android 애플리케이션을 크로스 플랫폼으로 만들었다면 다음 단계로 진행할 수 있습니다.
Compose Multiplatform을 사용하여 모든 플랫폼에서 통합된 UI를 만들 수 있습니다.
- Compose Multiplatform 및 Jetpack Compose에 대해 알아보기
- Compose Multiplatform 가용 리소스 탐색하기
- 공유 로직과 UI가 있는 앱 만들기
커뮤니티 리소스도 확인해 보세요.
