https://pl-coding.com/jetpack-compose-mistakes
이 글은 위의 Philip Lackner 님의 글을 번역 및 정리한 글 입니다.
이어서 작성하도록 하겠습니다.
Overusing hardcoded DP Units
때때로 우리는 고정된 크기를 가진 Composable 를 가지고 있지만, 그렇지 않은 경우가 많습니다. 이때 하드코딩된 DP 값을 사용하는 것을 피하고 이를 상대적인 크기의 modifier 로 바꾸세요.
fillMaxSize(), weight() 또는 widthIn() 이 그 예시 입니다.
// BAD
@Composable
fun LoginScreen(state: LoginState) {
Column {
TextField(
value = state.email,
onValueChange = { /* ... */ },
modifier = Modifier.width(300.dp)
)
}
}
위에 예시 코드에서는 텍스트 필드가 모든 화면 크기에 대해 가로로 300 dp 의 크기를 가집니다. 단지 하나의 디바이스에서 테스트 했을 때, 괜찮아 보인다고 해서 그것이 더 작은 혹은 더 큰, 또는 테블릿 에서도 괜찮다는 것은 아닙니다.
// GOOD
@Composable
fun LoginScreen(state: LoginState) {
Column {
TextField(
value = state.email,
onValueChange = { /* ... */ },
modifier = Modifier
.widthIn(max = 400.dp)
.fillMaxWidth()
)
}
}
대신에 상대적인 사이즈를 사용하세요. (또한 위의 코드와 같이 최대 고정 크기를 조합할 수 있습니다.) 이 텍스트 필드는 이제 핸드폰에 대해 가로 전체 영역을 차지 합니다. 하지만 테블릿에서도 텍스트 필드가 가로 전체 영역을 차지한다면 이상해보일 수 있습니다. 추가된 옵션을 통해 테블릿과 같은 디바이스에서는 가로 400dp 의 고정된 크기를 가질 것입니다.
Forgetting about touce target size
클릭 가능한 Composable 를 만들 때는, 터치 대상 크기를 고려해야 합니다. 만약 Composable 의 크기가 작다면, 사용자는 터치하는데 어려움이 있을 수 있습니다. 아래와 같은 Composable 을 만들 때 이를 고려해보세요.
// BAD
@Composable
fun OptionsButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Options",
modifier = Modifier.clickable { onClick() }
)
}
아이콘의 크기는 보통 손가락의 크기보다 작기 때문에 작은 면적 때문에 클릭하는데 있어 좌절을 겪을 수 있습니다.
// GOOD
@Composable
fun OptionsButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
IconButton(
onClick = onClick,
modifier = modifier
) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Options"
)
}
}
대신에 IconButton 을 사용해보세요, 터치 대상 크기를 충분히 크게 고려하고 있기 때문입니다. 물론 이는 클릭 가능한 버튼에 국한된 것은 아닙니다. 다른 모든 클릭 가능한 Composable 들을 포함 합니다.
Not checking View Decomposition Strategy in Fragments
Compose 는 XML 과 훌륭한 상호운용성을 제공합니다. 하지만 ComposeView 를 Fragment 내부에서 사용할 때, Composition이 적절하게 파괴(삭제)되기 위한 view decomposition 을 적절하게 설정해야 합니다. 기본적으로 Composition은 ComposeView가 창(window) 에서 분리될 때 파괴(삭제)되며 이것은 순수한 Jetpack Compose 앱을 위한 것 입니다. 그러나 Compose 를 점진적으로 추가하는 앱에서는 원하지 않는 상황이 발생할 수 있습니다.
// BAD
class LoginFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
// Composable screen
}
}
}
}
위에 예시 코드처럼 그냥 ComposeView 를 사용하는 것은 Composition이 Fragment 의 생명주기 내에 포함되는 것을 보장할 수 없습니다. 그리고 이것은 특정 환경에서 state 의 유실을 발생 시킬 수 있습니다.
// GOOD
class LoginFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
// Composable screen
}
}
}
}
대신에 Fragment 의 생명주기에 딱 맞도록(포함 되도록)하는 view decomposition 을 설정 하세요.
(참고로, 이는 Fragment 을 사용할 때에만 요구되는 사항입니다.)
상호 운용성 API 와 View Composition Strategy 와 관련한 자세한 내용은
아래 글을 참고 해주세요.
https://developer.android.com/jetpack/compose/interop/interop-apis?hl=ko
https://velog.io/@beokbeok/ViewCompositionStrategy
Mixing state naming
UI 개발에 있어서 State 를 생성하고 이름을 짓는 2가지 방법이 있습니다.
1. 화면에 어떤 종류의 영향을 주는지를 기반으로 이름을 지을 수 있습니다.
2. 그 상태가 나타내는 논리적인 동작(행동)을 기반으로 이름을 지을 수 있습니다.
어떤 접근법을 사용하든 그것은 당신에게 달렸지만, 하나를 골랐다면 한가지의 접근 방식을 지속적으로 유지하는게 중요합니다.
// BAD
data class LoginState(
val emailText: String = "",
val isProgressBarVisible: Boolean = false,
val isLoginFailed: Boolean = false
)
isProgressBarVisible 이라는 변수명은 1) 번 방법을 기반으로 이름을 지은 것입니다.('화면'에 프로그래스바가 보이는지, 보이지 않는지 여부), 하지만 isLoginFailed 는 2) 번 방법에 의거한 논리적인 동작(행동)을 기반으로 이름을 지은 것 입니다.(isLoginFailed 같은 경우는 화면에 어떤 영향을 주는지는 직접적으로 드러나있지는 않기 때문입니다). 두 가지의 이름을 짓는 방법을 전부 사용하고 있기 때문에 일관되자 않습니다.
// GOOD
data class LoginState(
val emailText: String = "",
val isLoggingIn: Boolean = false,
val isLoginFailed: Boolean = false
)
위 예시 코드에서 일관성을 유지하는 더 나은 방법을 제안합니다. isProgressBarVisible 이라는 상태의 이름을 isLogginIn으로 변경 했습니다. 따라서 이 이름은 동작(행동)을 반영합니다. 이 통일된 이름을 짓는 방법을 적용 함으로써, 각각의 Composable은 그 자체로 이 상태에서 어떤 행동을 하고 싶은 것인지 결정할 수 있습니다.
Forgetting about making Columns scrollable
당신의 구성한 화면이 하나의 Column 내부에 많은 Composable 함수들을 포함하고 있다면 딱 맞게 설정된 것 처럼 보이더라도 Scoll을 지원할 수 있는 것을 고려해야 합니다. 테스트하는 디바이스에서는 잘 맞는 것 처럼 보여도 그것이 더 작은 화면을 가진 디바이스에서도 맞는 것을 의미하는 것은 아니기 때문입니다.
// BAD
@Composable
fun AgendaScreen() {
Column {
// Lots of composables
}
}
// GOOD
@Composable
fun AgendaScreen() {
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
// Lots of composables
}
}
다음 글
13번을 읽고 현재 진행하고 있는 프로젝트가 xml + Compose로 구현해야해서 Fragment위에 ComposeView로 컴포즈 화면을 뛰우고 있는데,
저걸 적용하려고 보니까 SAA 프로젝트에서는 기본값으로도 충분하다고 하네요...!
(공식 문서에도 있씁니다!)
그럼 이제 저 ViewCompositionStratey는 언제 유용하나 했더니 화면을 꽉 채우지 않는 Fragment에서 사용할 때,
화면의 일부분으로 Fragment가 들어갈 경우 View가 Window에서 제거되지 않을 때도 있다고 하네요.
ex) ViewPager2 등등...
결론적으로 현재 프로젝트는 SAA여서 기본전략으로도 충분하다 였지만, 인사이트는 넓혀졌습니다.
공식문서를 꼼꼼히 읽어봐야겠어요, 이런 내용이 있는 지도 몰랐네요 ..
굳굳굳굳 잘 읽고 갑니다.
감사합니다 !!