[Compose Multiplatform] moko-resources 를 이용하여 font 적용하기

이지훈·2024년 1월 3일
0

서두

저번 글에서 언급했던 moko-resources 를 이용한 폰트 적용을 다뤄보도록 하겠다.

CMP 로 개발 중인 토이 프로젝트 앱에서 사용중인 버튼의 텍스트에 폰트를 적용하기 위해 moko-resources 라이브러리를 프로젝트에 주입해보았다.

공식 문서를 참고해서 차근차근 라이브러리의 사용을 위한 환경 설정을 해주면 다음과 같다.(strings 의 경우 국제화를 위한 추가 설정이 필요한데 그런 부분은 신경써주지 않아도 되서 비교적 간단하다.)

Android setup

1. gradle/libs.versions.toml 내에 moko-resources 의존성 추가

[versions]
moko-resources = "0.23.0"

[libraries]
moko-resources-generator = { group = "dev.icerock.moko", name = "resources-generator", version.ref = "moko-resources" }
moko-resource = { group = "dev.icerock.moko", name = "resources", version.ref = "moko-resources" }
moko-resource-compose = { group = "dev.icerock.moko", name = "resources-compose", version.ref = "moko-resources" }

[bundles]
moko-resources = ["moko-resource", "moko-resource-compose"]

2. Root build.gradle.kts

// 추가 
buildscript {
    dependencies {
        classpath(libs.moko.resources.generator)
    }
}

moko-resources 레포에서는 이렇게 추가하라고 나와있을텐데, 위의 설정을 제외한 나머지 설정들은 settings.gradle.kts 에 이미 존재하기에 위에 설정만 추가해줘도 성공적으로 빌드가 가능했다.

3. commonMain build.gradle.kts

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.jetbrainsCompose)
    id ("dev.icerock.mobile.multiplatform-resources") // 추가
}
...
android {
    namespace = ""
    compileSdk = libs.versions.android.compileSdk.get().toInt()

    sourceSets {
        named("main") {
            manifest.srcFile("src/androidMain/AndroidManifest.xml")
            res.srcDirs("src/androidMain/res")
            resources.srcDirs("src/commonMain/resources")
            java.srcDirs("build/generated/moko/androidMain/src")
        }
    }

    defaultConfig {
        applicationId = ""
        minSdk = libs.versions.android.minSdk.get().toInt()
        targetSdk = libs.versions.android.targetSdk.get().toInt()
        versionCode = 1
        versionName = "1.0"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    dependencies {
        debugImplementation(libs.compose.ui.tooling)
        implementation(libs.coil.compose)
        commonMainApi(libs.bundles.moko.resources) // 추가 
    }
}

multiplatformResources {
    multiplatformResourcesPackage = "${packageName}" <- 프로젝트의 packageName 추가 
}

multiplatformResourcesClassName = "SharedRes" // optional, default MR
을 추가해줄 수 있는데, MR(moko-resources의 약자)도 나쁘지 않아, 그대로 두었다.

일단 이렇게 하면 Android 에서 해줘야할 setting 은 끝이어서, font 파일을 추가해주었다.

다음과 같이 ComposeApp 모듈내에 commonMain 내부를 보면 resources 패키지가 존재하는데 그 내부에 MR/fonts 패키지를 생성한 후에 사용할 font 파일들(ttf, otf)을 추가하면 된다.

그렇게 하면 이제 자동으로 아래와 같은 코드가 생성되는데

composable 함수내에서는 다음과 같이 Pretendard-Semibold의 중간의 - 를 기준으로 분리되어 Pretendard.semiBold 이런식으로 접근이 가능해진다.(컨벤션이 그러하니, font 파일명을 접근하기 용이하게 바꾸어주었다.)

       Text(
            text = text,
            modifier = Modifier.padding(horizontal = 16.dp),
            color = White,
            fontFamily = fontFamilyResource(MR.fonts.Pretendard.semiBold), // <-
            fontWeight = FontWeight.SemiBold,
            fontStyle = FontStyle.Normal,
            fontSize = 18.sp,
            lineHeight = 27.sp,
        )

문제 발생

이제 빌드를 해보려고 하는데...

위에 사진에서의 생성된 expect object 인 MR에서 에러가 발생하였다.
공식문서에 나와 있는 대로 모든 세팅을 해줬는데...

문제 해결

하면서 이제 레포에 등록된 이슈를 뒤져보았고, 같은 문제를 겪는 분들을 많이 찾아볼 수 있었다.

https://github.com/icerockdev/moko-resources/issues/531

이슈에 달린 덧글들을 읽어보며, agp 버전을 최신 버전인 8.2.0++ 로 올려보았으나, 에러가 해결되진 않았고

https://github.com/icerockdev/moko-resources/issues/510#issuecomment-1619141070
해당 comment 나에겐 유효했다.

comment 에서 알려준 방법대로 commonMain 에 다음과 같이 추가 세팅을 적용해주었다.

kotlin {
    sourceSets {
        val desktopMain by getting

		// 추가 
        val iosX64Main by getting {
            resources.srcDirs("build/generated/moko/iosX64Main/src")
        }
        // 추가
        val iosArm64Main by getting {
            resources.srcDirs("build/generated/moko/iosArm64Main/src")
        }
        // 추가
        val iosSimulatorArm64Main by getting {
            resources.srcDirs("build/generated/moko/iosSimulatorArm64Main/src")
        }
        ...
    }
}

android {
    namespace = "org.moneyking.imagepicker"
    compileSdk = libs.versions.android.compileSdk.get().toInt()

    sourceSets {
        named("main") {
            manifest.srcFile("src/androidMain/AndroidManifest.xml")
            res.srcDirs("src/androidMain/res")
            resources.srcDirs("src/commonMain/resources")   
           	// 추가
           	java.srcDirs("build/generated/moko/androidMain/src") 
        }
    }
}

이후 빌드를 해보았고, 성공적으로 Android 에서 폰트가 적용된 것을 확인할 수 있었다.

폰트 적용 전폰트 적용 후

iOS setup

iOS 에서도 마찬가지로 font 를 적용하기 위해선 iOS 프레임워크에서의 사용을 위한 복사 작업이 필요로 하기에, 아래와 같은 script 를 Xcode로 실행한 프로젝트 내에 추가해줘야 한다.

Xcode 의 custom script 관련한 자세한 설명은 아래 글을 참고하시면 좋습니다.
Xcode custom script 시작하기

jetbrains 공식 홈페이지의 compose-multiplatform project wizard 로 만든 project 의 경우 pods 폴더가 iOSApp 모듈내에 존재하지 않아, 아래의 case(Without org.jetbrains.kotlin.native.cocoapods) 에 해당 하는 것 같다.(위의 case 로도 빌드가 되는지 확인해보기 위해 복붙을 해보았는데, resource 복사 작업이 제대로 실행되지 않는지, .otf 파일이 존재하지 않는다는 에러를 뱉으면서 build 가 되지 않았다.)


참고로 KMP 프로젝트를 생성할 때 iOS Framework Distribution 탭에서 cocoapods dependency manager 를 골라 프로젝트를 생성할 경우 위와 같이 Pods 패키지가 존재하는 것을 확인할 수 있었다.

따라서 아래의 script 를 복붙해줘야 하는데 어디에??? Xcode 넘 어렵...
수소문 끝에 복붙할 위치를 알아낼 수 있었고

좌측 하단의 TARGETS -> iosApp 을 클릭한 후에, 우측 상단에 Build Phase 로 진입한 후에, 좌측 상단에 + 버튼을 눌러서 [New Run script Phase] 를 클릭해준다.

최하단에 생성된 Run script에(최하단이 아니라면, 최하단으로 옮겨주기)

# Type a script or drag a script file from your workspace to insert its path.
"$SRCROOT/../gradlew" -p "$SRCROOT/../" :composeApp:copyFrameworkResourcesToApp \
    -Pmoko.resources.PLATFORM_NAME="$PLATFORM_NAME" \
    -Pmoko.resources.CONFIGURATION="$CONFIGURATION" \
    -Pmoko.resources.ARCHS="$ARCHS" \
    -Pmoko.resources.BUILT_PRODUCTS_DIR="$BUILT_PRODUCTS_DIR" \
    -Pmoko.resources.CONTENTS_FOLDER_PATH="$CONTENTS_FOLDER_PATH" 

아래의 command 를 입력해주되, :composeApp 이라고 써있는 부분은 프로젝트마다 다르기에 본인의 project 에 맞게 변경해줘야한다.(:yourframeworkproject)

상대 경로가 위와 다를 수 도 있으므로 유의...(머리 아파)

마찬가지로 jetbrains 공식 홈페이지의 compose-multiplatform project wizard 로 만든 project 의 경우, 위와 같이 그대로 작성해주면 된다.

이제 빌드를 해주면 다행히 iOS 에뮬레이터에서도 정상적으로 폰트가 적용되어, 실행되는 것을 확인할 수 있었다.

moko-resources 를 사용할 수 있는 환경 설정을 구축하는게 성공하였으므로, 토이 프로젝트를 고도화하면서 font 외에도 strings(국제화 분기 포함), images 등도 추가해보도록 해야겠다.

라이브러리의 이슈를 해결하고, Xcode Custom Script 작성하는 곳에서 에러를 해결하느라고 하루를 써버렸다...

뭐든 처음 할 때는 어려운게 당연한거다.
한번 해보면 할만 하다. 계속해서 성공 경험을 늘려가보는 것으로!

moko-resources 에 대한 환경 설정을 완료한 관계로 다음 글에서는 국제화(i18n) 까지 고려해야할 String Resources 관련 글을 적어보도록 하겠다.

참고)
https://github.com/icerockdev/moko-resources
https://github.com/icerockdev/moko-resources/issues/531
https://github.com/icerockdev/moko-resources/issues/510#issuecomment-1619141070
https://velog.io/@jmseb3/kmm%EC%97%90%EC%84%9C-resource-%EA%B3%B5%EC%9C%A0%ED%95%98%EA%B8%B0
https://medium.com/@boobalaninfo/article-1-compose-multiplatform-moko-resource-integration-dbccbf19aab7
https://life-shelter.tistory.com/240
https://velog.io/@yeahg_dev/Xcode-custom-script-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0
https://medium.com/icerock/moko-resources-0-21-with-compose-multiplatform-support-462d8b11116b

profile
실력은 고통의 총합이다. Android Developer

1개의 댓글

comment-user-thumbnail
2024년 5월 3일

https://www.jetbrains.com/help/kotlin-multiplatform-dev/whats-new-compose-1-6-0.html#improved-resources-api-all-platforms
-> 1.6.0 패치 이후로 이제는 moko-resources 를 사용하지 않아도 resources 들을 플랫폼간에 공유할 수 있게 되었다! moko-resources ㅂㅂ2

답글 달기