Relay 및 Jetpack Compose를 사용하여 완전한 앱 빌드하기

1. 시작하기 전에

Relay는 팀이 Figma에서 UI 구성요소를 디자인하고 바로 Jetpack Compose 프로젝트에서 사용할 수 있게 지원하는 도구 키트입니다. 지루한 디자인 사양 및 QA 단계가 필요 없어, 팀이 훌륭한 Android UI를 신속하게 제공할 수 있습니다.

이 Codelab에서는 Relay UI 패키지를 Compose 개발 프로세스에 통합하는 방법을 알아봅니다. 처음부터 끝까지의 워크플로가 아닌 통합 기법에 집중합니다. Relay의 일반 워크플로에 관해 알아보려면 Relay 기본 튜토리얼을 참고하세요.

기본 요건

  • Compose의 기본 사용 경험. 아직 하지 않았다면 Jetpack Compose 기본사항 Codelab을 완료합니다.
  • Kotlin 문법 사용 경험

학습할 내용

  • UI 패키지를 가져오는 방법
  • UI 패키지를 탐색 및 데이터 아키텍처와 통합하는 방법
  • 컨트롤러 로직으로 UI 패키지를 래핑하는 방법
  • Figma 스타일을 Compose 테마에 매핑하는 방법
  • 생성된 코드에서 UI 패키지를 기존 컴포저블로 바꾸는 방법

빌드할 항목

  • 디자이너가 제공한 Relay 패키지를 기반으로 현실적인 앱 디자인을 구현합니다. Reflect라는 앱으로, 마음챙김과 좋은 습관을 실천하도록 돕는 일일 추적 앱입니다. 여기에는 다양한 유형의 추적기 모음과 이러한 추적기를 추가하고 관리하는 UI가 포함됩니다. 앱은 다음 이미지와 같습니다.

완성된 앱

필요한 항목

2. 설정

코드 가져오기

이 Codelab의 코드를 가져오려면 다음 중 하나를 실행합니다.

$ git clone https://github.com/googlecodelabs/relay-codelabs
  • GitHub에서 relay-codelabs 저장소로 이동하고 원하는 브랜치를 선택한 다음 Code > Download zip(코드 > ZIP 다운로드)을 클릭하여 다운로드한 ZIP 파일을 압축해제합니다.

어떤 경우이든 main 브랜치에는 시작 코드가 있고 end 브랜치에는 솔루션 코드가 포함되어 있습니다.

Android 스튜디오용 Relay 플러그인 설치하기

아직 Android 스튜디오용 Relay 플러그인이 없는 경우 다음 단계를 따르세요.

  1. Android 스튜디오에서 Settings > Plugins를 클릭합니다.
  2. 텍스트 상자에 Relay for Android Studio를 입력합니다.
  3. 검색 결과에 표시된 확장 프로그램에서 Install을 클릭합니다.

Android 스튜디오 플러그인 설정

  1. Third-party plugins privacy note 대화상자가 표시되면 Accept를 클릭합니다.
  2. OK > Restart를 클릭합니다.
  3. Confirm exit 대화상자가 표시되면 Exit를 클릭합니다.

Android 스튜디오와 Figma 연결하기

Relay는 Figma API를 사용하여 UI 패키지를 검색합니다. 이 API를 사용하려면 무료 Figma 계정개인 액세스 토큰이 필요하며, 필요한 항목 섹션에도 나열되어 있습니다.

아직 Android 스튜디오를 Figma에 연결하지 않았다면 다음 단계를 따르세요.

  1. Figma 계정에서 페이지 상단의 프로필 아이콘을 클릭하고 Settings(설정)를 선택합니다.
  2. Personal access tokens(개인 액세스 토큰) 섹션의 텍스트 상자에 토큰 설명을 입력한 다음 Enter(또는 macOS의 경우 return)를 누릅니다. 토큰이 생성됩니다.
  3. Copy this token(이 토큰 복사)을 클릭합니다.

Figma에서 생성된 액세스 토큰

  1. Android 스튜디오에서 Tools > Relay Settings를 선택합니다. Relay settings 대화상자가 표시됩니다.
  2. Figma Access Token 텍스트 상자에 액세스 토큰을 붙여넣은 다음 OK를 클릭합니다. 환경이 설정되었습니다.

3. 앱 디자인 검토하기

Reflect 앱은 디자이너와 함께 앱의 색상, 서체, 레이아웃, 동작을 정의했습니다. 앱이 Material 구성요소 및 테마와 원활하게 연동하도록 Material Design 3 규칙을 준수하여 이 디자인을 만들었습니다.

홈 화면 검토하기

홈 화면에는 사용자 정의 추적기의 목록이 표시됩니다. 활성 날짜를 변경하고 다른 추적기를 만들 수 있는 어포던스도 제공됩니다.

홈 화면

디자이너는 Figma에서 이 화면을 여러 구성요소로 나누고, API를 정의하고, Figma용 Relay 플러그인으로 구성요소를 패키징했습니다. 개발자는 패키징된 구성요소를 Android 스튜디오 프로젝트에 가져올 수 있습니다.

홈 화면 구성요소

추가/수정 화면 검토하기

추가/수정 화면에서 사용자는 추적기를 추가하거나 수정할 수 있습니다. 표시되는 형식은 추적기 유형에 따라 약간 다릅니다.

추가/수정 화면

마찬가지로 이 화면은 패키징된 구성요소 여러 개로 나뉩니다.

추가/수정 화면 구성요소

테마 검토하기

이 디자인의 색상과 서체는 Material Design 3 토큰 이름에 기반하여 Figma 스타일로 구현됩니다. 이에 따라 Compose 테마 및 Material 구성요소와의 상호 운용성이 높아집니다.

Figma 스타일

4. UI 패키지 가져오기

UI 패키지를 프로젝트에 가져오려면 먼저 디자인 소스를 Figma에 업로드해야 합니다.

Figma 소스 링크를 가져오려면 다음 단계를 따르세요.

  1. Figma에서 Import file(파일 가져오기)을 클릭한 후 Codelab 프로젝트 폴더에 있는 ReflectDesign.fig 파일을 선택합니다.
  2. 파일을 마우스 오른쪽 버튼으로 클릭한 다음 Copy link(링크 복사)를 선택합니다. 다음 섹션에서 필요합니다.

a98d24b4d5ee5c34.png

프로젝트에 UI 패키지 가져오기

  1. Android 스튜디오에서 ./CompleteAppCodelab 프로젝트를 엽니다.
  2. File > New > Import UI Packages를 클릭합니다. Import UI Packages 대화상자가 표시됩니다.
  3. Figma source URL 텍스트 상자에 이전 섹션에서 복사한 URL을 붙여넣습니다.

Import UI Packages 대화상자

  1. App theme 텍스트 상자에서 com.google.relay.example.reflect.ui.theme.ReflectTheme를 입력합니다. 이렇게 하면 생성된 미리보기에 맞춤 테마가 사용됩니다.
  2. Next를 클릭합니다. 파일의 UI 패키지가 미리보기로 표시됩니다.
  3. Create를 클릭합니다. 패키지를 프로젝트에 가져옵니다.
  4. Project 탭으로 이동한 다음 ui-packages 폴더 옆에 있는 2158ffa7379d2b2e.png 펼치기 화살표를 클릭합니다.

ui-packages 폴더

  1. 패키지 폴더 중 하나의 옆에서 2158ffa7379d2b2e.png 펼치기 화살표를 클릭하면 JSON 소스 파일 및 애셋 종속 항목이 포함되어 있음을 볼 수 있습니다.
  2. JSON 소스 파일을 엽니다. Relay 모듈에 패키지 및 API의 미리보기가 표시됩니다.

Relay 패키지 미리보기 모듈

코드 빌드 및 생성하기

  1. Android 스튜디오 상단에서 b3bc77f3c78cac1b.png Make project를 클릭합니다. 각 패키지의 생성된 코드가 java/com.google.relay.example.reflect 파일에 추가됩니다. 생성된 컴포저블에 Figma 디자인의 모든 레이아웃 및 스타일 지정 정보가 포함됩니다.
  2. 필요한 경우 코드 창과 미리보기 창이 서로 나란히 표시되도록 Split를 클릭합니다.
  3. range/Range.kt 파일을 열면 각 구성요소 변형의 Compose 미리보기가 생성된 것을 볼 수 있습니다.

c0d21ab0622ad550.png

5. 구성요소 통합하기

이 섹션에서는 Switch 추적기의 생성된 코드를 자세히 살펴봅니다.

Switch 추적기의 디자인

  1. Android 스튜디오에서 com/google/relay/example/reflect/switch/Switch.kt 파일을 엽니다.

Switch.kt(생성됨)

/**
 * This composable was generated from the switch UI Package.
 * Generated code; don't edit directly.
 */
@Composable
fun Switch(
    modifier: Modifier = Modifier,
    isChecked: Boolean = false,
    isPressed: Boolean = false,
    emoji: String = "",
    title: String = ""
) {
    TopLevel(modifier = modifier) {
        if (isChecked) {
            ActiveOverlay(modifier = Modifier.rowWeight(1.0f).columnWeight(1.0f)) {}
        }
        if (isPressed) {
            State(modifier = Modifier.rowWeight(1.0f).columnWeight(1.0f)) {}
        }
        TopLevelSynth {
            Label(modifier = Modifier.rowWeight(1.0f)) {
                Emoji(emoji = emoji)
                Title(
                    title = title,
                    modifier = Modifier.rowWeight(1.0f)
                )
            }
            if (isChecked) {
                Checkmark {
                    Vector(modifier = Modifier.rowWeight(1.0f).columnWeight(1.0f))
                }
            }
        }
    }
}
  1. 다음 사항에 유의하세요.
  • Figma 디자인의 모든 레이아웃스타일 지정이 생성됩니다.
  • 하위 구성요소는 개별 컴포저블로 분리됩니다.
  • 모든 디자인 변형의 컴포저블 미리보기가 생성됩니다.
  • 색상 및 서체 스타일은 하드코딩됩니다. 나중에 수정하세요.

추적기 삽입

  1. Android 스튜디오에서 java/com/google/relay/example/reflect/ui/components/TrackerControl.kt 파일을 엽니다. 이 파일은 습관 추적기에 데이터 및 상호작용 로직을 제공합니다. 현재 이 구성요소는 추적기 모델에서 원시 데이터를 출력합니다.

7850337c9ba23fd5.png

  1. com.google.relay.example.reflect.switch.Switch 패키지를 파일에 가져옵니다.
  2. trackerData.tracker.type 필드로 결정되는 when 블록을 만듭니다.
  3. when 블록 본문에서 유형이 TrackerType.BOOLEAN인 경우 Switch() Composable 함수를 호출합니다.

코드는 다음과 같이 표시됩니다.

TrackerControl.kt

when (trackerData.tracker.type) {
    TrackerType.BOOLEAN ->
        Switch(
          title = trackerData.tracker.name,
          emoji = trackerData.tracker.emoji
        )
    else ->
        Text(trackerData.tracker.toString())
}
  1. 프로젝트를 다시 빌드합니다. 이제 홈페이지에서 Switch 추적기가 디자인에 따라 실시간 데이터로 올바르게 렌더링됩니다.

f07eda1a7740129b.png

6. 상태 및 상호작용 추가하기

UI 패키지는 스테이트리스(Stateless)입니다. 렌더링되는 것은 전달된 매개변수의 단순 결과입니다. 하지만 실제 앱에는 상호작용 및 상태가 필요합니다. 다른 매개변수와 마찬가지로 상호작용 핸들러를 생성된 컴포저블에 전달할 수 있습니다. 하지만 이러한 핸들러가 조작하는 상태를 어디에 유지해야 할까요? 동일한 핸들러가 모든 인스턴스에 전달되는 것을 방지하려면 어떻게 할까요? 패키지의 컴포지션을 재사용 가능한 컴포저블에 어떻게 추상화할 수 있을까요? 이런 경우에는 생성된 패키지를 맞춤 Composable 함수에 래핑하는 것이 좋습니다.

컨트롤러 Composable 함수에서 UI 패키지 래핑하기

컨트롤러 Composable 함수에서 Wrapping UI 패키지를 래핑하면 프레젠테이션 또는 비즈니스 로직을 맞춤설정할 수 있고 필요에 따라 로컬 상태를 관리할 수 있습니다. 개발자가 래퍼 코드를 업데이트하지 않고도 디자이너는 여전히 자유롭게 Figma의 원래 UI 패키지를 업데이트할 수 있습니다.

Switch 추적기의 컨트롤러를 만들려면 다음 단계를 따르세요.

  1. Android 스튜디오에서 java/com/google/relay/example/reflect/ui/components/SwitchControl.kt 파일을 엽니다.
  2. SwitchControl() Composable 함수에서 다음 매개변수를 전달합니다.
  • trackerData: TrackerData 객체
  • modifier: 데코레이터 객체
  • onLongClick: 수정 및 삭제를 위한 추적기 길게 누르기를 지원하는 상호작용 콜백

modifier

  1. 클릭 및 길게 누르기를 처리하도록 combinedClickable 수정자를 Switch() 함수에 전달합니다.
  2. isToggled() 메서드를 포함하여 TrackerData 객체의 값을 Switch() 함수에 전달합니다.

완성된 SwitchControl() 함수는 다음 코드 스니펫과 같습니다.

SwitchControl.kt

package com.google.relay.example.reflect.ui.components

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.relay.example.reflect.model.Tracker
import com.google.relay.example.reflect.model.TrackerData
import com.google.relay.example.reflect.model.TrackerType
import com.google.relay.example.reflect.switch.Switch

/*
 * A component for controlling switch-type trackers.
 *
 * SwitchControl is responsible for providing interaction and state management to the stateless
 * composable [Switch] generated by Relay. [onLongClick] provides a way for callers to supplement
 * the control's intrinsic interactions with, for example, a context menu.
 */
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SwitchControl(
    trackerData: TrackerData,
    modifier: Modifier = Modifier,
    onLongClick: (() -> Unit)? = null,
) {
    Switch(
        modifier
            .height(64.dp)
            .clip(shape = RoundedCornerShape(size = 32.dp))
            .combinedClickable(onLongClick = onLongClick) {
                trackerData.toggle()
            },
        emoji = trackerData.tracker.emoji,
        title = trackerData.tracker.name,
        isChecked = trackerData.isToggled(),
    )
}

@Preview
@Composable
fun SwitchControllerPreview() {
    val data = TrackerData(
        Tracker(
            emoji = "🍕",
            name = "Ate Pizza",
            type = TrackerType.BOOLEAN
        )
    )
    SwitchControl(data)
}
  1. TrackerControl.kt 파일에서 Switch 가져오기를 삭제한 후 Switch() 함수를 SwitchControl() 함수 호출로 바꿉니다.
  2. TrackerType.RANGETrackerType.COUNT 열거자 상수의 사례를 추가합니다.

완성된 when 블록은 다음 코드 스니펫과 같습니다.

SwitchControl.kt

when (trackerData.tracker.type) {
    TrackerType.BOOLEAN ->
        SwitchControl(
            trackerData = trackerData,
            onLongClick = { expanded = true },
        )
    TrackerType.RANGE ->
        RangeControl(
            trackerData = trackerData,
            onLongClick = { expanded = true },
        )
    TrackerType.COUNT ->
        ValueControl(
            trackerData = trackerData,
            onLongClick = { expanded = true },
        )
}
  1. 프로젝트를 다시 빌드합니다. 이제 추적기를 표시하고 상호작용할 수 있습니다. 홈 화면이 완료되었습니다.

b23b94f0034243d3.png

7. 기존 구성요소 매핑하기

Relay를 사용하면 개발자는 UI 패키지를 기존 컴포저블로 바꿔 생성된 코드를 맞춤설정할 수 있습니다. 코드에서 즉시 사용 가능한 구성요소 또는 맞춤 디자인 시스템을 출력하기 위한 좋은 방법입니다.

텍스트 필드 매핑하기

다음 이미지는 Add/edit tracker(추적기 추가/수정) 대화상자의 Switch Tracker Editor 디자인입니다.

Switch 설정 구성요소의 디자인

Google 디자이너는 디자인에 ReflectTextField를 사용했으며, 이는 Material Design 3 텍스트 필드를 기반으로 빌드한 코드에 이미 구현되어 있습니다. Figma는 기본적으로 텍스트 필드를 지원하지 않으므로, Relay에서 생성된 기본 코드는 디자인과 비슷하게 보일 뿐이며 기능 컨트롤이 아닙니다.

이 요소를 실제 구현으로 대체하려면 두 가지 사항, 즉 텍스트 필드 UI 패키지매핑 파일이 필요합니다. 다행히 Google 디자이너는 이미 Figma에 디자인 시스템 구성요소를 패키징했고 Tracker Editor의 버튼 구성요소를 디자인에 사용했습니다. 기본적으로 이러한 중첩된 패키지는 설정 바 패키지의 종속 항목으로 생성되지만 개발자는 구성요소 매핑을 사용하여 이를 바꿉니다.

Relay 플러그인이 오버레이된 텍스트 필드용 Figma 구성요소

매핑 파일 만들기

Android 스튜디오용 Relay 플러그인에서는 구성요소 매핑 파일을 만드는 바로가기를 제공합니다.

매핑 파일을 만들려면 다음 단계를 따르세요.

  1. Android 스튜디오에서 text_field UI 패키지를 마우스 오른쪽 버튼으로 클릭한 후 Generate mapping file을 선택합니다.

Generate mapping file 컨텍스트 메뉴 항목

  1. 파일에서 다음 코드를 입력합니다.

text_field.json

{
  "target": "ReflectTextField",
  "package": "com.google.relay.example.reflect.ui.components",
  "generatePreviews": false
}

구성요소 매핑 파일은 Compose 클래스 타겟과 패키지 및 선택적인 fieldMapping 객체 모음을 식별합니다. 이러한 필드 매핑을 통해 패키지 매개변수를 예상되는 Compose 매개변수로 변환할 수 있습니다. 이 경우 API가 동일하므로 개발자는 타겟 클래스만 지정하면 됩니다.

  1. 프로젝트를 다시 빌드합니다.
  2. trackersettings/ TrackerSettings.kt 파일에서 생성된 TitleFieldStyleFilledStateEnabledTextConfigurationsInputText() 구성 가능한 함수를 찾아서 이 함수에 생성된 ReflectTextField 구성요소가 있음을 확인합니다.

TrackerSettings.kt(생성됨)

@Composable
fun TitleFieldStyleFilledStateEnabledTextConfigurationsInputText(
    onTitleChanged: (String) -> Unit,
    title: String,
    modifier: Modifier = Modifier
) {
    ReflectTextField(
        onChange = onTitleChanged,
        labelText = "Title",
        leadingIcon = "search",
        trailingIcon = "cancel",
        supportingText = "Supporting text",
        inputText = title,
        state = State.Enabled,
        textConfigurations = TextConfigurations.InputText,
        modifier = modifier.requiredHeight(56.0.dp)
    )
}

8. Compose 테마에 매핑하기

기본적으로 Relay는 색상 및 서체의 리터럴 값을 생성합니다. 이에 따라 변환의 정확성이 보장되지만 구성요소가 Compose 테마 설정 시스템을 사용하지 못하게 됩니다. 이러한 현상은 어두운 모드에서 앱을 볼 때 도드라집니다.

어두운 모드를 사용 중이며 잘못된 색상을 표시하는 홈 화면 미리보기

일 탐색 구성요소가 거의 보이지 않으며 색상이 잘못되었습니다. 이 문제를 해결하려면 Relay의 스타일 매핑 기능을 사용하여 생성된 코드에서 Figma 스타일을 Compose 테마 토큰에 연결합니다. 이렇게 하면 Relay와 Material Design 3의 구성요소 간에 시각적 일관성이 높아지며 어두운 모드를 지원할 수 있습니다.

1fac916db14929bb.png

스타일 매핑 파일 만들기

  1. Android 스튜디오에서 src/main/ui-package-resources/style-mappings 폴더로 이동한 후 다음 코드가 포함된 figma_styles.json 파일을 만듭니다.

figma_styles.json

{
  "figma": {
    "colors": {
      "Reflect Light/background": "md.sys.color.background",
      "Reflect Dark/background": "md.sys.color.background",
      "Reflect Light/on-background": "md.sys.color.on-background",
      "Reflect Dark/on-background": "md.sys.color.on-background",
      "Reflect Light/surface": "md.sys.color.surface",
      "Reflect Dark/surface": "md.sys.color.surface",
      "Reflect Light/on-surface": "md.sys.color.on-surface",
      "Reflect Dark/on-surface": "md.sys.color.on-surface",
      "Reflect Light/surface-variant": "md.sys.color.surface-variant",
      "Reflect Dark/surface-variant": "md.sys.color.surface-variant",
      "Reflect Light/on-surface-variant": "md.sys.color.on-surface-variant",
      "Reflect Dark/on-surface-variant": "md.sys.color.on-surface-variant",
      "Reflect Light/primary": "md.sys.color.primary",
      "Reflect Dark/primary": "md.sys.color.primary",
      "Reflect Light/on-primary": "md.sys.color.on-primary",
      "Reflect Dark/on-primary": "md.sys.color.on-primary",
      "Reflect Light/primary-container": "md.sys.color.primary-container",
      "Reflect Dark/primary-container": "md.sys.color.primary-container",
      "Reflect Light/on-primary-container": "md.sys.color.on-primary-container",
      "Reflect Dark/on-primary-container": "md.sys.color.on-primary-container",
      "Reflect Light/secondary-container": "md.sys.color.secondary-container",
      "Reflect Dark/secondary-container": "md.sys.color.secondary-container",
      "Reflect Light/on-secondary-container": "md.sys.color.on-secondary-container",
      "Reflect Dark/on-secondary-container": "md.sys.color.on-secondary-container",
      "Reflect Light/outline": "md.sys.color.outline",
      "Reflect Dark/outline": "md.sys.color.outline",
      "Reflect Light/error": "md.sys.color.error",
      "Reflect Dark/error": "md.sys.color.error"
    },
    "typography": {
      "symbols": {
        "Reflect/headline/large": "md.sys.typescale.headline-large",
        "Reflect/headline/medium": "md.sys.typescale.headline-medium",
        "Reflect/headline/small": "md.sys.typescale.headline-small",
        "Reflect/title/large": "md.sys.typescale.title-large",
        "Reflect/title/medium": "md.sys.typescale.title-medium",
        "Reflect/title/small": "md.sys.typescale.title-small",
        "Reflect/body/large": "md.sys.typescale.body-large",
        "Reflect/body/medium": "md.sys.typescale.body-medium",
        "Reflect/body/small": "md.sys.typescale.body-small",
        "Reflect/label/large": "md.sys.typescale.label-large",
        "Reflect/label/medium": "md.sys.typescale.label-medium",
        "Reflect/label/small": "md.sys.typescale.label-small"
      },
      "subproperties": {
        "fontFamily": "font",
        "fontWeight": "weight",
        "fontSize": "size",
        "letterSpacing": "tracking",
        "lineHeightPx": "line-height"
      }
    }
  },
  "compose": {
    "colors": {
      "md.sys.color.background": "MaterialTheme.colorScheme.background",
      "md.sys.color.error": "MaterialTheme.colorScheme.error",
      "md.sys.color.error-container": "MaterialTheme.colorScheme.errorContainer",
      "md.sys.color.inverse-on-surface": "MaterialTheme.colorScheme.inverseOnSurface",
      "md.sys.color.inverse-surface": "MaterialTheme.colorScheme.inverseSurface",
      "md.sys.color.on-background": "MaterialTheme.colorScheme.onBackground",
      "md.sys.color.on-error": "MaterialTheme.colorScheme.onError",
      "md.sys.color.on-error-container": "MaterialTheme.colorScheme.onErrorContainer",
      "md.sys.color.on-primary": "MaterialTheme.colorScheme.onPrimary",
      "md.sys.color.on-primary-container": "MaterialTheme.colorScheme.onPrimaryContainer",
      "md.sys.color.on-secondary": "MaterialTheme.colorScheme.onSecondary",
      "md.sys.color.on-secondary-container": "MaterialTheme.colorScheme.onSecondaryContainer",
      "md.sys.color.on-surface": "MaterialTheme.colorScheme.onSurface",
      "md.sys.color.on-surface-variant": "MaterialTheme.colorScheme.onSurfaceVariant",
      "md.sys.color.on-tertiary": "MaterialTheme.colorScheme.onTertiary",
      "md.sys.color.on-tertiary-container": "MaterialTheme.colorScheme.onTertiaryContainer",
      "md.sys.color.outline": "MaterialTheme.colorScheme.outline",
      "md.sys.color.primary": "MaterialTheme.colorScheme.primary",
      "md.sys.color.primary-container": "MaterialTheme.colorScheme.primaryContainer",
      "md.sys.color.secondary": "MaterialTheme.colorScheme.secondary",
      "md.sys.color.secondary-container": "MaterialTheme.colorScheme.secondaryContainer",
      "md.sys.color.surface": "MaterialTheme.colorScheme.surface",
      "md.sys.color.surface-variant": "MaterialTheme.colorScheme.surfaceVariant",
      "md.sys.color.tertiary": "MaterialTheme.colorScheme.tertiary",
      "md.sys.color.tertiary-container": "MaterialTheme.colorScheme.tertiaryContainer"
    },
    "typography": {
      "symbols": {
        "md.sys.typescale.display-large": "MaterialTheme.typography.displayLarge",
        "md.sys.typescale.display-medium": "MaterialTheme.typography.displayMedium",
        "md.sys.typescale.display-small": "MaterialTheme.typography.displaySmall",
        "md.sys.typescale.headline-large": "MaterialTheme.typography.headlineLarge",
        "md.sys.typescale.headline-medium": "MaterialTheme.typography.headlineMedium",
        "md.sys.typescale.headline-small": "MaterialTheme.typography.headlineSmall",
        "md.sys.typescale.title-large": "MaterialTheme.typography.titleLarge",
        "md.sys.typescale.title-medium": "MaterialTheme.typography.titleMedium",
        "md.sys.typescale.title-small": "MaterialTheme.typography.titleSmall",
        "md.sys.typescale.body-large": "MaterialTheme.typography.bodyLarge",
        "md.sys.typescale.body-medium": "MaterialTheme.typography.bodyMedium",
        "md.sys.typescale.body-small": "MaterialTheme.typography.bodySmall",
        "md.sys.typescale.label-large": "MaterialTheme.typography.labelLarge",
        "md.sys.typescale.label-medium": "MaterialTheme.typography.labelMedium",
        "md.sys.typescale.label-small": "MaterialTheme.typography.labelSmall"
      },
      "subproperties": {
        "font": "fontFamily",
        "weight": "fontWeight",
        "size": "fontSize",
        "tracking": "letterSpacing",
        "line-height": "lineHeight"
      }
    },
    "options": {
      "packages": {
        "MaterialTheme": "androidx.compose.material3"
      }
    }
  }
}

테마 매핑 파일은 두 개의 최상위 수준 객체 figmacompose로 구성됩니다. 이 객체 내부에서 색상 및 유형 정의는 중간 토큰을 통해 두 환경 간에 연결됩니다. 따라서 여러 Figma 스타일을 단일 Compose 테마 항목에 매핑할 수 있으며 이 방법은 밝은 테마와 어두운 테마를 지원할 때 유용합니다.

  1. 매핑 파일을 검토합니다. 특히, Figma의 서체 속성이 예상되는 Compose 속성에 다시 매핑되는 방식을 확인합니다.

UI 패키지 다시 가져오기

매핑 파일을 만든 후에는 모든 UI 패키지를 프로젝트에 다시 가져와야 해야 합니다. 매핑 파일이 제공되지 않아 첫 가져오기 때 모든 Figma 스타일 값이 삭제되었기 때문입니다.

UI 패키지를 다시 가져오려면 다음 단계를 따르세요.

  1. Android 스튜디오에서 File > New > Import UI Packages를 클릭합니다. Import UI Packages 대화상자가 표시됩니다.
  2. Figma source URL 텍스트 상자에 Figma 소스 파일의 URL을 입력합니다.
  3. Translate Figma styles to Compose theme 체크박스를 선택합니다.
  4. Next를 클릭합니다. 파일의 UI 패키지가 미리보기로 표시됩니다.
  5. Create를 클릭합니다. 패키지를 프로젝트에 가져옵니다.

Import UI Packages 대화상자

  1. 프로젝트를 다시 빌드한 다음 switch/Switch.kt 파일을 열어 생성된 코드를 봅니다.

Switch.kt(생성됨)

@Composable
fun ActiveOverlay(
    modifier: Modifier = Modifier,
    content: @Composable RelayContainerScope.() -> Unit
) {
    RelayContainer(
        backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
        isStructured = false,
        radius = 32.0,
        content = content,
        modifier = modifier.fillMaxWidth(1.0f).fillMaxHeight(1.0f)
    )
}
  1. Compose 테마 객체에서 backgroundColor 매개변수가 MaterialTheme.colorScheme.surfaceVariant 필드로 설정된 방식을 확인합니다.
  2. 미리보기 창에서 앱을 어두운 모드로 전환합니다. 테마가 올바르게 적용되고 시각적 버그가 수정됩니다.

6cf2aa19fabee292.png

9. 축하합니다

축하합니다. Relay를 Compose 앱에 통합하는 방법을 알아봤습니다.

자세히 알아보기