원티드 앱에 Compose 적용해보기 — 5

Nampyo Jeong
원티드랩 기술 블로그
9 min readNov 12, 2021

(부제: TextField 잔혹사)

이 글은 compose 1.0.1 버전을 기반으로 작성되었습니다.

지난 글과 달리 이번에는 compose 도입을 보류하게 된 일에 대해 얘기해보려고 합니다.

사건의 발단은 커뮤니티 게시글 작성 화면을 compose로 변환하다 TextField에서 2가지 이슈가 발생한 것에서 시작했습니다. 첫번째 이슈는 우회할 수 있는 방법을 찾아 적용했지만 두번째 이슈의 명확한 해결 방법을 찾지 못해 더 이상 작업을 진행하지 못하고 보류하게 되었고, 이번 글에서 이 두 가지 이슈에 대한 내용을 작성해보겠습니다.

NestedScroll 이슈

게시글 작성 화면은 제목, 본문을 작성하는 TextField가 각각 하나씩 있고 이 두 컴포넌트를 감싸고 있는 Column에 스크롤이 있는 형태입니다.

@Composable
private fun CommunityWritingBody(
viewModel: CommunityWritingViewModel,
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = scrollState)
.padding(horizontal = 15.dp, vertical = 28.dp),
) {
...

ContentTextField(viewModel)
}
}

@Composable
private fun ContentTextField(
viewModel: CommunityWritingViewModel,
) {
val contentText by viewModel.contentText.observeAsState("")

BasicTextField(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 15.dp)
.defaultMinSize(minHeight = 150.dp),
value = contentText,
onValueChange = { viewModel.onContentChanged(it) },
textStyle = TextStyle(
color = colorResource(id = R.color.neutral_gray_900),
fontSize = 16.sp,
lineHeight = (17.7).sp,
),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
),
cursorBrush = SolidColor(colorResource(
id = R.color.w_blue
)),
decorationBox = { innerTextField ->
if (contentText.isEmpty()) {
TextFieldHint(
hintRes = R.string.user_community_writing_textbox_content_placeholder,
fontSize = 16.sp,
)
}
innerTextField()
},
)
}

여기서 본문은 multi line이 입력 가능하기 때문에 height는 wrap_content로 되어있고 본문에서 엔터키를 누르면 사이즈가 커지면서 상위 Column의 스크롤이 커서가 계속 보일 수 있게 따라 움직여줘야 합니다.
하지만 compose에서는 scroll이 움직이지 않고 그대로 커서는 화면 밖으로 사라져 버렸습니다.

이 문제를 해결하기 위해서 저는 onSizeChanged를 활용해서 TextField의 height가 변하는만큼 스크롤을 움직여 주는 방식을 사용했습니다.

@Composable
private fun CommunityWritingBody(
viewModel: CommunityWritingViewModel,
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = scrollState)
.padding(horizontal = 15.dp, vertical = 28.dp),
) {
...

ContentTextField(viewModel, scrollState)
}
}

@Composable
private fun ContentTextField(
viewModel: CommunityWritingViewModel,
scrollState: ScrollState,
) {
val contentText by viewModel.contentText.observeAsState("")
val coroutineScope = rememberCoroutineScope()
var prevHeight by remember { mutableStateOf(0) }

BasicTextField(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 15.dp)
.defaultMinSize(minHeight = 150.dp)
.onSizeChanged {
val diff = it.height - prevHeight
prevHeight = it.height
if (prevHeight == 0 || diff == 0) {
return@onSizeChanged
}

coroutineScope.launch {
scrollState.animateScrollTo(
scrollState.value + diff
)
}
},

value = contentText,
onValueChange = { viewModel.onContentChanged(it) },
textStyle = TextStyle(
color = colorResource(id = R.color.neutral_gray_900),
fontSize = 16.sp,
lineHeight = (17.7).sp,
),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
),
cursorBrush = SolidColor(colorResource(
id = R.color.w_blue
)),
decorationBox = { innerTextField ->
if (contentText.isEmpty()) {
TextFieldHint(
hintRes = R.string.user_community_writing_textbox_content_placeholder,
fontSize = 16.sp,
)
}
innerTextField()
},
)
}

약간 부자연스러운 느낌이 있지만 그래도 다행히 원하는 동작을 하게 만들 순 있었습니다.

Back button 이슈

Compose TextField에서는 이전 EditText와는 다르게 back button을 눌렀을 때 TextField에서 포커스가 제거되는 동작이 있었습니다. 그 때문에 글 작성 화면에서 onBackPressed가 호출되려면 백 버튼을 한 번 더 눌러야 합니다.

키보드 사라짐 -> 포커스 제거됨 -> onBackPressed() 호출

어떻게 보면 사소한 이슈지만 기존과는 다른 동작을 보이기 때문에 UX에 치명적이라고 생각되어 몇가지 방안을 시도해봤지만 결국 성공하지 못했습니다.

다행히 compose 1.1.0-alpha04 버전에서는 해당 이슈가 수정되어 정상동작하는 것을 확인했지만, 아직 alpha 버전이고 compile sdk 버전과 kotlin 버전도 함께 올려줘야 해서 stable 버전이 나올 때까진 compose 도입을 보류하기로 했습니다.

마무리

이번 글은 compose 적용하기 시리즈의 마지막 글입니다. 시리즈 마무리가 실패기라는 점은 조금 아쉽지만 compose를 적용하는 과정이 재밌었고 기술블로그 글까지 작성하게 되어 저에겐 꽤 의미있는 시간이었습니다.

하루빨리 compose가 더 많은 기능을 제공하고 안정화되어 compose만으로 모든 UI를 만들 수 있길 바라며 시리즈를 마칩니다. 지금까지 부족한 제 글을 읽어주셔서 감사합니다.

원티드에서는 다양한 직군에서 적극적으로 채용중입니다! 서버, 웹, 앱, 디자인 등 제품을 만들어가는 각자의 분야에서 전문적인 분들과 함께 일하기를 기대하고 있습니다. 회사 채용 정보 페이지를 확인해 주세요!

--

--