Compose 무작정 맛보기 [3. 메인 화면]

ricky_0_k·2022년 3월 26일
0

Compose 맛보기

목록 보기
4/7

서론

자 메인 화면이다. (학점을 아름답게 볼 수 있는 화면)
메인화면 개발 당시에는 xml 내에 jetpack compose 를 어떻게 쓸 수 있는지 에 중점을 두어 개발을 했었다.

참고로 공식문서에서는 xml 과 jetpack compose 의 호환이 매우 좋다고 했었다.
몇 퍼센트였는지는 기억이 안나지만, 좋다고 했으므로 정말 그런지 한 번 확인해보자.

메인 화면 리펙터링

먼저 한 작업은 MVP 코드를 MVVM 으로 변환하는 작업이었다.
더불어 BindingAdapter 를 활용하여 총 학점에 따라 배경이나 버튼 색상이 달라지던 코드를 개선했다.

(참고로, 사진에 보이는 두 번째, 세 번째 함수는 이후에 xml 에 compose 를 넣으면서 없어졌다.)

어쨌든 당시에는 이런 식으로 리펙터링했다만, 잘 한건지는 모르겠다.
기존에 한번에 처리했던 걸 쪼개 놓았는데, 변경될 일도 없는거긴 해서 괜히했나 싶긴 하다만
오른쪽보다 왼쪽이 덕지덕지 붙어있는 느낌인건 왜일까 싶다.
어쨌든 이런 과정을 거치면서 코드 라인은 10~20줄 정도 줄였다.

Custom ProgressBar 리펙터링

일단 이름부터 바꿔야 했다.
기존엔 MainProgressBar 였는데, 통계화면에서도 쓰이기 때문에 범용적인 이름 변경이 필요했다.

그리고 기존 Java 코드를 Kotlin 으로 바꾸었고
주석 보강, 함수 및 반복적인 로직 정리 등을 통해 코드를 정리해 나갔다.
그래서 보기에는 좋아졌지만 이건 오히려 코드가 늘었다. 한 3~40줄 정도?
코드를 다시 이해하면서 주석이 없어 불편했다보니 그것들을 추가했고 그러면서 코드라인이 많이 늘은 것 같다.

예시를 들면 이렇다고 할까...?
그 외에도 애매한 함수 명을 변경하는 등 코드를 다시봐도 이해하기 위한 방향으로 리펙터링을 했다.

xml 에 compose 같은 걸 끼얹나?

자 사실상 본론이다. 과연 xml 에 compose 는 잘 적용이 될까?

놀랍게도 그렇다 위의 xml 처럼 기존의 레이아웃을 ComposeView 로 처리하고
onCreate 에서 layoutId.setContent { ... } 하여
람다 내에 Composable 함수를 호출해주면 된다. 코드로 보면 이렇다.

binding.clMainInput.setContent {
    MainBottomItemView(viewModel, MAIN_BOTTOM_ITEM_INPUT)
}

binding.clMainStatistic.setContent {
    MainBottomItemView(viewModel, MAIN_BOTTOM_ITEM_STATISTIC)
}

MAIN_BOTTOM_ITEM_INPUT, MAIN_BOTTOM_ITEM_STATISTIC 은 어떤 버튼인지에 대한 flag 값이다.

그리고 파란색 내의 각 버튼은 사실상 기능이 똑같다.

  1. 클릭하여 다른 화면으로 넘어감
  2. 총 학점 현황에 따라 이미지가 다름

이렇게 2개 기능밖에 없기에 이 기능들을 통합했다.
그래서 기존에 리펙터링하면서 각 버튼의 bindingAdapter 로직을 Composable 함수에 넣었다.

그리고 동일한 기능이라는 건 통합도 가능하다는 말이다.
위의 setContent 코드에서 예상한 분들도 있었겠지만
Composable 함수의 두번째 파라미터 값이 각각 어떤 이미지, 클릭 이벤트를 쓸 것인지를 분기한다.

val title = arrayOf(
    stringResource(id = R.string.tv_main_input),
    stringResource(id = R.string.tv_main_statistic)
)[position]

val drawables = arrayOf(
    listOf(
        R.drawable.m_input_a,
        R.drawable.m_input_b,
        R.drawable.m_input_c,
        R.drawable.iv_main_input
    ),
    listOf(
        R.drawable.m_statistic_a,
        R.drawable.m_statistic_b,
        R.drawable.m_statistic_c,
        R.drawable.iv_main_statistic
    )
)[position]

val clickEvent = viewModel?.let {
    arrayOf(
        it::clickInputLayout,
        it::clickStatisticLayout,
    )[position]
} ?: { Timber.i("for Preview") }

이런 식으로 처리했으며 bindingAdapter 에서 중복 코드가 있어 아쉬웠던 내용을 이런 방식으로 개선했다.

val mainScore = viewModel?.mainScore?.observeAsState() ?: remember { mutableStateOf(0f) }

Column(
	// 1
    modifier = Modifier.clickable(
        enabled = true,
        onClick = clickEvent,
        indication = rememberRipple(bounded = true),
        interactionSource = remember { MutableInteractionSource() }
    ),
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.Center
) {
    Image(
        painter = painterResource(
            id = mainScore.value?.let {
            	// 2
                when {
                    it >= 4 -> drawables[0]
                    it >= 3 && it < 4 -> drawables[1]
                    it >= 2 && it < 3 -> drawables[2]
                    else -> drawables[3]
                }
            } ?: drawables[3]
        ),
        contentDescription = title
    )
    Spacer(modifier = Modifier.height(32.dp))
    Text(
        text = title,
        fontSize = 15.sp,
        color = colorResource(id = R.color.defaultTextColor),
        textAlign = TextAlign.Center
    )
}

실제 View 는 이렇다.

  1. 이전 포스팅에서는 ripple 을 없앴지만, 여기에서는 ripple 을 넣었다.
    indication, interactionSource 를 설정해야 하며, 이렇게 하면 ripple 효과가 발생한다.
  2. 위에서 말했던 중복 로직을 통합한 내용이다.

결론

생각보다 적을 내용이 없을 정도로 간단했다.
우리는 앞으로 ComposeView 에 setContent 를 설정하여 Composable 함수를 호출해주면
xml 내에 녹아드는 Compose 를 볼 수 있을 것이다.

여기까지는 되게 간단했었다. 하지만... 다음장 (학점 입력 화면) 에서 정말 한 것이 많았다.
commit 기록을 돌아보아도 여기 리펙터링 과정이 한 2주정도 걸린 것 같다.
당연히 온전히 100% 2주는 아니고, 짜투리 시간에서 2주이지만 그래도 다른 작업에 비해 오래 걸렸다.

  1. state 관리하기
  2. 상위 TextField 사용하기
  3. focus 에 따라 실시간 학점 계산하도록 하기

많은 작업이 있었고 다음 포스팅에서 요 내용들을 다뤄보려 한다.

profile
valuable 을 추구하려 노력하는 개발자

0개의 댓글