안드로이드 Theme와 GetStream Theme를 알아보고 CompositionLocalProvider의 역할을 알아본다.(GetStream 후원글)
개인 광고 영역
Compose Theme는 어떻게 적용하고 활용할 수 있을까?
- Material Theme에 따라서 사용할까?
- 우리만의 디자인 시스템 색상 값과 필요한 정보를 포함하여 Custom theme 구성?
이런 고민은 한 번쯤? 해볼 수 있을 것 같다.
본 글은 GetStream의 후원으로 작성된 글로 다음의 내용을 다루어보려 한다.
Material 3
사용하지 않는다면?Material 3
사용하여 테마 만들기?- SDK로 유명한
GetStream
에서는 Theme를 어떻게 사용하고 있을까? - 추가로 Theme 함수의 내부에는
CompositionLocalProvider
가 많던데?CompositionLocalProvider
을 좀 더 살펴보자.
본문을 통해 하나씩 이야기해 보겠다.
읽기 전에
- XML Theme는 다루지 않는다.
- Compose component에 대해 설명하지 않는다.
- Material 3에서 사용하는 Token 방식의 Color 지정을 논하진 않는다.
- GetStream 후원으로 작성한 글이다.
Compose에서 Theme가 필요할까?
Android에서 Theme가 필요할까? XML을 다룰 때는 특별히 테마를 신경 쓰지 않고, 필요할 때(투명, 바텀, alert 등) 사용하고, XML 코드에 대부분 색상을 직접 지정하였다.
Compose에서도 Theme 없이 만들 수 있지 않을까? Compose Theme가 필요한지 먼저 따져보기 전에 Android Studio에서 제공하는 기본 코드를 살펴보자.
Empty Activity 프로젝트를 생성하면 바로 확인할 수 있는 코드로, Material 3 기반 코드로 작성되어 있다.
setContent부터 MyApplicationTheme, Compose Component 조합으로 화면을 그리고 있다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyApplicationTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
기본 코드에서도 알 수 있지만 MyApplicationTheme {}
이 시작 부분에 적용됨을 알 수 있다.
여기서 고민해 볼 수 있는데 Theme {}
시작 부분을 꼭 가져야 할까?
답은, 없는것보단 적용하는 것이 좋으며, Material을 따르지 않더라도 CustomTheme를 가지는 것이 좋다.
우선 Theme가 없다면 아래와 같이 직접 코드 작성 시 Color와 TextStyle을 일일이 지정해야 한다.
Text(
text = "Hello $name!",
color = Color.Blue,
modifier = modifier
)
한두 줄이라면 크게 상관없겠지만 Theme라는 틀을 활용하면 요구사항을 빠르고, 유연하게 적용할 수 있다.
없는 것보단 있는 것이 당연하지만 좋다.(Material을 따르지 않고 CustomTheme 사용하는 것은 또 다른 이야기가 된다.)
구글에서 제공하는 Material 3
를 사용하지 않는다면?
Theme를 가지는 것은 당연하지만 좋은 선택이다. 하지만 Material 3
을 사용하는 것이 맞을까?
우선 Compose Material 3 라이브러리에는 아래와 같이 3개의 디펜던시를 필요로 한다.
- androidx.compose.material3 - Material3 Theme를 포함하여 UI 요소를 포함
- androidx.compose.foundation - UI 위에 가장 기본이 되는 Text/TextField 와 같은 부분을 적용
- androidx.compose.ui - Compose 레이아웃을 포함한 구성
- androidx.compose.runtime - 컴포즈의 프로그래밍 모델 및 상태 관리의 기본 구성 요소를 포함하는 핵심
공식 문서 Jetpack Compose architectural layering - link 참고
이 중 Material 3
를 포함하지 않는 것을 말하거나, Material 3
을 사용은 하지만 Theme는 따로 구성하는 것 역시 가능한 선택지이다.
여기서 2가지를 이야기해 보겠다.
Custom Theme를 만들면서 Material 3
를 포함하지 않는다면?
시간과 여유가 있다면 이 방식을 선택해도 무관하다. Compose 처음 나왔을 때는 Material Theme
를 사용하지 않아도 된다는 이야기들이 많았었다.
하지만 Material Theme
기반으로 제공해 주는 UI가 상당한데, 모든 Components 정보는 Material 3의 Components - 링크을 참고하길
- Scaffold : TopBar, BottomBar, Content 영역을 포함하는 틀이다.
- Text : 기본 Material UI 가이드를 포함
- Checkbox
- RadioButton
- BottomSheet
- Snackbar
등 이후에 새로 추가되는 요소들도 여기에 모두 포함된다. Material 3를 처음부터 사용하지 않고 만들겠다면 이와 동일한 코드를 직접 작성하겠다는 의미이거나, 내부 코드를 복사해서 우리만의 Theme를 만들겠다는 의미이다.
이걸 정말 하는 것이 맞을까는 다른 이야기다. 이 모든 걸 다 직접 만들고 관리할 수 있는가?
어차피 선택은 팀과 개인의 자유이지만 Color 정도의 Custom만으로도 충분히 좋은 코드를 만들 수 있다는 점에서는 이 방식이 좋은 선택지인지는 모르겠다.
Custom Theme를 만들면서 Material 3
를 포함한다면?
필자는 이 방식으로 내부에서 사용할 Components를 재정의하여 사용하고 있고, Theme 역시 Material 3를 따르도록 설계해두었다.
2년 전만 해도 이렇게 문서에 이 색상이 어디에서 적용되는지 알지 못하였지만 이젠 문서만 잘 보아도 이 색상을 어디에서 사용할 수 있는지 알 수 있다.
이걸 기반으로 Custom Theme를 내부에 정의하여 사용하고 있는데 아래와 같은 형태다.
object SomeColor {
@Stable
val Color = Color(0xFF000000)
}
@Immutable
data class SomeColorScheme(
val primary: Color,
//... Material 3 참고 color 정의
) {
// 내부 코드 참고
}
@Composable
fun SomeTheme(
content: @Composable () -> Unit,
) {
MaterialTheme(
colorScheme = SomeColor.Colors.toMaterialTheme(),
content = content,
)
}
마지막에 toMaterialTheme
를 이용해서 한번 복사하는 과정을 거치는데 이유는 Scaffold를 보면 알 수 있는데 다음과 같이 사용하고 있기 때문에 MaterialTheme
를 한번 감싸서 처리하고 있다.
@Composable
fun Scaffold(
containerColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(containerColor),
content: @Composable (PaddingValues) -> Unit
)
모든 Components를 내부용으로 만들 것이라면 불필요한 작업이다.
답은 없다.
두가지 방법 중 2번째 방식을 선택한 이유와 고민 포인트는 아래와 같다.
- 모든 컴포넌트를 내부에서 재정의하려면 시간이 걸린다.
- 따로 디자인 시스템을 가지는 것은 아니니 공통화의 목적으로 컴포넌트를 만들어간다.
- 신경 안 쓰고 사용하더라도 기본 Theme는 따라갈 수 있도록 만들면 좋겠다.
- 내부의 SomeScaffold를 사용하지 않더라도 그냥 Scaffold를 사용하여도 동일하게 나와야 한다.
여기서 중요한 포인트 lint
를 만들어 사용하면 SomeScaffold 대신 Scaffold 사용하지 않게 만들 수 있지만 이런 부분을 고려하지 않았다.
결국 차이라고 해봐야 Material Theme를 호출할 것인가 말 것인가의 차이다.
Material Theme를 호출하였을 경우에는 dynamicColor
역시 대응이 가능하다. 하지만 보통의 회사 서비스에서는 이를 고려할 이유는 많지 않을 것 같지만 개인은 충분히 선택할 수 있는 선택지이다.
Material 3 Theme를 따라보자.
Material 3 Theme를 사용한다는 것은 결국 ColorCode를 적절한 위치에 위치시켜야 한다는 이야기이다. Theme를 만드는 데 있어서 Color가 어느 지점에 적절하게 사용되어야 하는지를 알아야 하는데, 위에서도 언급했지만 다행히 구글이 문서를 갱신해 주었고, 적절한 위치의 색상 사용을 알 수 있다는 것이다.
Material 3 Theme - 링크에서 Components를 확인.
적용할 수 있는 Color set은 아래와 같은데 이 값들만 36가지 종류를 가진다는 점이다. 이 값을 모두 매핑해서 사용하기엔 부담이 크다.
여담이지만 필자는 구글이 문서를 업데이트하기 전 이미 이 작업을 하였는데, 색상이 어디에서 사용되는지 일일이 다 찾아서 사용하였는데 이와 관련한 글을 남겼었다.
안드로이드 Compose Material 2 컬러 정보를 알아보자 - 링크
이 중에 중요하다고 생각 드는 부분만 몇 가지 남기면?
-
이 부분은 GPT를 통한 번역
- primary: 앱의 화면과 구성 요소에서 가장 자주 표시되는 주요 색상이다.
- onPrimary: 주요 색상 위에 표시되는 텍스트와 아이콘에 사용되는 색상이다.
- secondary: 보조 색상은 제품을 강조하고 구별하는 더 많은 방법을 제공합니다. 보조 색상은 다음에 가장 적합하다
- onSecondary: 보조 색상 위에 표시되는 텍스트와 아이콘에 사용되는 색상이다.
- tertiary: 주요 색상과 보조 색상의 균형을 맞추거나 입력 필드와 같은 요소에 더 높은 주의를 끌기 위해 사용할 수 있는 제3의 색상이다.
- onTertiary: 제3의 색상 위에 표시되는 텍스트와 아이콘에 사용되는 색상이다.
- background: 스크롤 가능한 콘텐츠 뒤에 나타나는 배경색이다.
- onBackground: 배경색 위에 표시되는 텍스트와 아이콘에 사용되는 색상이다.
- surface: 카드, 시트, 메뉴와 같은 구성 요소의 표면에 영향을 미치는 표면 색상이다.
- onSurface: 표면 색상 위에 표시되는 텍스트와 아이콘에 사용되는 색상이다.
- error: 오류 색상은 텍스트 필드의 잘못된 텍스트와 같이 구성 요소의 오류를 나타내는 데 사용한다.
- onError: 오류 색상 위에 표시되는 텍스트와 아이콘에 사용되는 색상이다.
그래도 이만큼이다. 다행히 내부적으로 머트리얼 2에서 이상하게 사용하던 코드도 제거되어서 머트리얼 3는 그나마 괜찮아졌다는 점이다.
이 코드를 기반으로 ColorScheme을 정의하고, 이를 Theme에 주입해 주는 역할을 하면 되는데, 기본으로 생성해 준 코드에서는 아래와 같다.
3가지 파라메터로 Dark theme 여부, dynamicColor 여부, 마지막 람다 표현으로 content 시작점을 사용할 수 있는데, 결국 MaterialTheme
에 colorScheme, typography, content를 지정하여 사용하게 된다는 점이다.
@Composable
fun MyApplicationTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
이렇게 MaterialTheme
를 사용하여 지정한 코드에서 특정 컬러를 적용하고 싶다면 MaterialTheme.colorScheme.primary
로 호출하여 사용한다.
이 부분이 이미 적용되어 있는 Scaffold는 아래와 같이 사용하기만 해도 기본 또는 dynamicColor에 따라 색상이 적용된다.
@Composable
fun Screen() {
Scaffold {
Box(modifier = Modifier.padding(it)) {}
}
}
MaterialTheme에는 어떤 걸 포함하고 있을까?
GetStream의 코드를 보기 전에 MaterialTheme에는 어떤 걸 외부로 제공하고 있는지 살펴보고 넘어가 보자.
- colorScheme : 현재 테마의 색상 정보를 가져올 수 있으며
MaterialTheme.colorScheme.xxx
으로 불러서 사용 - typography : 현재 테마의 폰트 정보를 가지며
MaterialTheme.typography.xxx
로 불러서 사용 - shapes : 현재 테마의 shapes 정보를 가지며
MaterialTheme.shapes.xxx
로 불러서 사용
object MaterialTheme {
/**
* Retrieves the current [ColorScheme] at the call site's position in the hierarchy.
*/
val colorScheme: ColorScheme
@Composable
@ReadOnlyComposable
get() = LocalColorScheme.current
/**
* Retrieves the current [Typography] at the call site's position in the hierarchy.
*/
val typography: Typography
@Composable
@ReadOnlyComposable
get() = LocalTypography.current
/**
* Retrieves the current [Shapes] at the call site's position in the hierarchy.
*/
val shapes: Shapes
@Composable
@ReadOnlyComposable
get() = LocalShapes.current
}
이와 같이 Material Theme를 지정해두고 사용할 수 있다. 내부적으로 SomeTheme
를 사용한다면 이와 같은 클래스를 직접 만들어 활용하게 되는 것이다.
GetStream Theme 알아보기 전에
Material 3의 Theme는 일반적인 앱에서 활용할 수 있는 정도의 가장 기본 가이드를 제공해 준다. 반면 GetStream - 링크 APIs and SDKs to Build In-App Chat & Video & Feeds Faster.
에서 사용하기 위한 SDK 용도로 확장해 사용하고 있다.
Material 3의 Theme 샘플을 통해서도 확장 가능하지만 SDK에서 활용될 수 있는 점을 적절하게 이용하면 Theme 사용을 좀 더 넓은 의미로 확장해 사용할 수 있음을 알 수 있으니 Theme를 사용하는 방식을 알아보자.
GetStream에서 사용하는 Theme
기본 Theme를 내부적으로 사용할 수 있도록 SomeTheme
를 만들어 사용할 수 있음을 확인하였다. 그럼 GetStream에서 제공하는 Theme는 어디까지 제공하고 있을까?
GetStream의 코드를 보기 전에 문서를 먼저 살펴보았다.
이 문서에는 생각보다 Theme를 더 넓은 의미로 활용하고 있음을 알 수 있는데, 아래와 같이 나열해 보았다.
- 기본 정보
- Get Stream chart 관련 기본 설정
- test 관련 옵션 제공
- 권한과 관련
- Audio/Video를 활용하기 위한 권한 처리
- 파일 첨부 처리를 도와주는 제어, 팩토리, 권한 관련
- Theme와 관련
- Theme에 대한 상세정보, 화면별로 필요한 Theme 정보, font 정보
- 기타
- Stream MediaRecorder
- DateFormatter
코드를 살펴보자
이 코드는 GetStream ChatTheme.kt - 링크의 코드 중 일부를 적은 부분인데, SDK로서의 역할을 할 수 있는 모든 부분을 담고 있다고 보아도 좋을 것 같다.
- SDK라면 지정해 준 정보를 내부에서 바꾸지 않고 그대로 활용함을 보장해야 하며
- 외부에서 주입해 주는 커스텀 역시 충분히 제공하는 것이 좋다.
라는 관점으로 보면 이 GetStream의 SDK로써는 충분히 유용하게 작성되었다는 점이다.
문맥상 매우 길어져서 몇 가지 정보만 가져와보았으니 참고해 보면 좋을 듯하다.
@Composable
public fun ChatTheme(
/* 생략 */
autoTranslationEnabled: Boolean = false, // 각종 옵션들
/* 생략 */
colors: StreamColors = if (isInDarkMode) StreamColors.defaultDarkColors() else StreamColors.defaultColors(), // color 정의
/* 생략 */
rippleTheme: RippleTheme = StreamRippleTheme, // Ripple 정의
attachmentFactories: List<AttachmentFactory> = StreamAttachmentFactories.defaultFactories(), // 파일 첨부 정보
/* 생략 */
allowUIAutomationTest: Boolean = false, // 테스트 관련
dateFormatter: DateFormatter = DateFormatter.from(LocalContext.current), // 채팅에 사용할 date formatter
/* 생략 */
messagePreviewFormatter: MessagePreviewFormatter = MessagePreviewFormatter.defaultFormatter(
context = LocalContext.current,
typography = typography,
attachmentFactories = attachmentFactories,
autoTranslationEnabled = autoTranslationEnabled,
),
/* 생략 */
attachmentsPickerTabFactories: List<AttachmentsPickerTabFactory> =
if (useDefaultSystemMediaPicker) {
AttachmentsPickerTabFactories.defaultFactoriesWithoutStoragePermissions()
} else {
AttachmentsPickerTabFactories.defaultFactories()
},
/* Theme 관련 생략 */
streamMediaRecorder: StreamMediaRecorder = DefaultStreamMediaRecorder(LocalContext.current),
content: @Composable () -> Unit,
)
함수 안에도 조금 살펴보자.
ChatTheme의 코드 중에 눈에 띄는 코드는 allowUIAutomationTest
옵션을 별도로 제공한다는 점이다.
allowUIAutomationTest - true/false
를 제공함으로써 semantics
를 활용한 UI Test 대응을 미리 해두었다는 점이다. 컴포즈에서 Ui test를 아직 시도하지는 않아서 자세한 내용은 문서를 참고해 보시길
그 외에는 LocalXXX
로 시작하는 코드들이 상당히 많이 보인다. LocalXXX
부분의 코드가 많이 보인다는 건 이 부분을 살펴보는 것이 도움이 될 수 있다는 점이다.
CompositionLocalProvider(
LocalColors provides colors,
/* 생략 */
LocalComposerLinkPreviewEnabled provides isComposerLinkPreviewEnabled,
) {
if (allowUIAutomationTest) {
Box(
modifier = Modifier.semantics { testTagsAsResourceId = allowUIAutomationTest },
) {
content()
}
} else {
content()
}
}
GetStream의 CompositionLocalProvider 적용 부분은?
Stream에서 사용하는 CompositionLocalProvider
는 모두 compositionLocalOf
을 활용하고 있다는 점이다.
사실 Theme의 시작점이고 값의 변경은 staticCompositionLocalOf
을 사용하여 UI가 다시 그려짐을 보장하여도 될 것 같은데, compositionLocalOf
을 사용하고 있다는 점이다.
private val LocalColors = compositionLocalOf<StreamColors> {
error("No colors provided! Make sure to wrap all usages of Stream components in a ChatTheme.")
}
private val LocalDimens = compositionLocalOf<StreamDimens> {
error("No dimens provided! Make sure to wrap all usages of Stream components in a ChatTheme.")
}
그리고 또 하나 중요한 포인트는 private으로 Theme 이외에서는 이 값들을 업데이트하지 않음을 보장해 주고 있다.
구글의 대표적인 Color 처리하는 코드는 아래와 같은데, Stream에서는 이 부분을 모두 private으로 적용한 부분을 그대로 활용할 수 있음을 보장한다.
val LocalContentColor = compositionLocalOf { Color.Black }
LocalContentColor는 언제 어디서든 값을 변경하고 업데이트할 수 있다.
GetStream은 Theme에서 CompositionLocalProvider를 활용하여 값을 부르는 건 알겠는데?
Theme의 범위를 어디까지 잡아야 할까? Stream의 코드 아랫 부분에 외부에서 사용할 수 있는 값들을 매핑하였는데 아래와 같다.
public object ChatTheme {
public val colors: StreamColors
@Composable
@ReadOnlyComposable
get() = LocalColors.current
/* 생략 */
/**
* Retrieves the current list of [StreamMediaRecorder] at the call site's position in the hierarchy.
*/
public val streamMediaRecorder: StreamMediaRecorder
@Composable
@ReadOnlyComposable
get() = LocalStreamMediaRecorder.current
}
모든 정보는 ChatTheme.colors.xxx
로 부르거나 ChatTheme.streamMediaRecorder.xxxx
로 바로 활용할 수 있다는 점이다.
그런데 Theme인데 streamMediaRecorder
를 바로 불러다 사용하는 것이 맞을까?
개인적으론 SDK라서 이를 활용하는 방식도 좋은 방법이라고 생각한다. 결국 CompositionLocalProvider
를 통해 사용 범위를 명확히 지정하여 사용하고 있고, 이 값들은 초기에 지정해 준 정보를 그대로 사용하고 있음을 명시적으로 작성해두었기 때문에 특별히 문제는 없는 듯하다.
하지만 일반적인 서비스에서 이렇게까지 하는 게 맞을까는 고민이 필요한 점이다.
SomeTheme.triggerEvent.xxxx // 이렇게 사용하는 것이 맞을까?
LocalTriggerEvent.current.xxxx // 이렇게 활용하는 것이 더 좋을까?
CompositionLocalProvider를 좀 더 알아보자.
CompositionLocalProvider
을 깊이 있게 탐구하는 것은 아니지만 문서에 나와있는 내용을 좀 더 알아보고 넘어가자.
결국 Theme는 CompositionLocalProvider를 활용한다.
CompositionLocalProvider
를 이용해 필요한 정보를 라이프 사이클에 맞게 불러다 활용할 수 있는데, Material 3 Theme
내부도 Stream에서의 코드와 마찬가지로 적용하고 있다.
MaterialTheme
는 크게 ColorScheme
, Shapes
, Typography
를 주입받고, 내부에서 필요한 정보를 추가로 Provider에 셋하고 있다.
@Composable
fun MaterialTheme(
colorScheme: ColorScheme = MaterialTheme.colorScheme,
shapes: Shapes = MaterialTheme.shapes,
typography: Typography = MaterialTheme.typography,
content: @Composable () -> Unit
) {
val rippleIndication = androidx.compose.material.ripple.rememberRipple()
val selectionColors = rememberTextSelectionColors(colorScheme)
CompositionLocalProvider(
LocalColorScheme provides colorScheme,
LocalIndication provides rippleIndication,
androidx.compose.material.ripple.LocalRippleTheme provides MaterialRippleTheme,
LocalShapes provides shapes,
LocalTextSelectionColors provides selectionColors,
LocalTypography provides typography,
) {
ProvideTextStyle(value = typography.bodyLarge, content = content)
}
}
이 중 colorScheme은 staticCompositionLocalOf
을 사용하여 초기화하고 있고,
internal val LocalColorScheme = staticCompositionLocalOf { lightColorScheme() }
selectionColors
은 compositionLocalOf
을 활용하고 있다.
val LocalTextSelectionColors = compositionLocalOf { DefaultTextSelectionColors }
결국 이 둘을 잘 알면 유용하게 사용할 수 있다는 의미이니 문서를 참고해 보자.
CompositionLocalProvider란?
Locally scoped data with CompositionLocal - 링크이란 공식 문서이다.
compostionLocalProvider에는 2개의 초기화 방법을 제공한다.
compositionLocalOf
: 현재 범위 내에 compositionLocal의 정보가 변경되었을 경우 해당 범위 내의 이 값을 사용하는 범위를 갱신- ContentColor를 범위 내에서 color 변경한다면 이 색상을 호출하여 사용하는 곳을 추적하여 UI를 갱신
staticCompositionLocalOf
: 이 값은 범위 내의 값을 추적하지 않고 그냥 전부 새로 그림.- ContentColor를 이 값으로 활용할 경우 화면을 전부 새로 그림
이 두 함수의 영향 범위는 매우 다름을 알 수 있다.
이전에 작성한 Compose에서 웹뷰를 유지하는 방법에서는 staticCompositionLocalOf
활용하는 방법을 소개했었으니 참고해 보셔도 좋다.
staticCompositionLocalOf
은 값이 명확하게 1회 초기화하며 이후에는 변경하지 않음을 명시할 경우 활용하는 것이 좋고, compositionLocalOf
는 언제든 변경되고, 이 값이 변경되었을 경우 값을 사용한 부분도 UI 갱신이 일어나야 하는 경우 이를 활용하는 것이 좋다.
이 내용을 깊이 있게 알고 싶다면 Jetpack Compose Internals의 4장 Compose UI, 측정 정책 (Measuring policies)
부분에서 매우 상세하게 다루고 있으니 책을 참고하여 추가 학습하시길
Jetpack Compose Internals 한국어 번역(번역 엄재웅, 류기민) - 링크
마무리
이 글은 GetStream의 후원을 받아 작성하였다. GetStream의 내부 Theme 활용을 글로 담았는데, SDK를 만든다면 이런 형태로의 Theme를 활용할 수 있구나를 파악할 수 있었다.
거기에 결국 Theme 값을 유지하면서 상태를 보장해 주는 방식을 private으로 잘 구분하여 사용해야 하며, Theme를 통해 더 많은 값을 불러다 활용할 수 있도록 만들어주는 것 역시 가능함을 알 수 있었다.
이런 방식에서는 사실 호불호가 생길 수 있긴 하다.
- Theme인데 왜 Theme와 관련 없는 설정 정보를 포함하는 것인가?
- 초기화만 하고, Theme가 아닌 접근 방법을 제공하면 더 좋은 거 아닐까?
이 부분은 코드 컨벤션과 관련이 있지 않을까 싶다. SomeTheme.value.xxxx
를 사용하였을 때 LocalXXX.current
를 기억하고 사용하는 행위를 반대로 줄일 수 있으니 말이다.
이 글로 Theme를 구성하는 전반적인 방법이나 CompositionLocal
사용을 다 알 수 있는 것은 아니지만 Theme를 이렇게까지 활용하고 접근 제어를 어떻게 하는 것이 좋을지만 이해해도 다행이라고 생각한다.
Material 3에서 사용하는 Token 방식의 Color 지정을 논하진 않았는데 이 방식은 figma와 밀접한 관련이 있다고 하니 아래 링크를 참고하시길
Comments