어쩌다?
프로젝트가 끝난지는 오래되었지만, 규모가 큰 기능을 새로 만들기보다는 기존의 프로젝트에 대해 의미있는 개선을 하자는것이 팀의 생각이었고, 저도 그렇게 생각했습니다.
프로젝트 시작 전 짧게나마 Compose를 경험해보고 편하고, 재미있다라는 생각이 들었지만, 실제 프로젝트에 적용하기에는 조금 무리가 있어서 XML로 개발을 했었습니다.
Compose 공부를 하자하자 했지만, 저는 특정 목적을 가지고 공부를 하는편이라 손이 잘 안가는것이 현실이었습니다,, ㅎㅎ
그러다 운이 좋게도 팀원분께서 Compose로 마이그레이션하자고 제안해주셔서 이번 프로젝트에서 맡았던 부분을 Compose로 바꿔보았고, 그에 대한 느낀점? 경험을 적어볼까 합니다.
주관적으로 느낀점이고, 코드도 정답인 코드는 아닌점 확인해주세요 ㅎ
Compose의 장점
미리 보기 지원
Preview 기능을 통해서 화면을 볼 수있는것은 개발의 속도를 정말 빠르게 해줬습니다.
xml도 물론 미리보기 기능을 지원하지만, Compose의 preview는 액션까지 실험해 볼 수 있고,
TextField와 같이 키보드로 이벤트까지 확인할 수 있어서 생산성이 훨씬 좋았다고 느꼈습니다.
기존의 xml 구조라면 해당 이벤트를 확인하기 위해서 해당 화면까지 가야되는 귀찮음이 있지만, preview를 통해서 그 화면에서 인터렉션을 할 수 있는것은 크나큰 장점인것 같습니다.
직관적인 코드와 순수 코틀린
Compose를 사용하면 순수 코틀린으로 UI를 구현할 수 있습니다.
물론 BindingAdapter를 통해서도 이를 간접적으로 해소할 수 있었지만, XML 내에서의 문법은 Java 문법을 따르고,
또 컴파일 과정에서 에러가 발생하면 방대한 양의 에러메시지와 추적하기가 어렵다는것이 개인적으로 느낀점이었습니다.
직관적인 코드를 느낄 수 있는 포인트는 몇개 있었습니다.
<com.google.android.material.button.MaterialButton
style="@style/Typography.Bottom02"
android:layout_width="264dp"
android:layout_height="50dp"
android:layout_marginTop="30dp"
android:onClick="@{()->view.onClickCompleteButton(@string/board_multipart_image_name)}"
android:text="@string/check_message"
android:backgroundTint="@{vm.uiState.boardName.length() > 0 && vm.uiState.boardName.length <=20 ? @color/main4 : @color/gray3}"
app:editButtonEnabled="@{vm.uiState.boardName}"
android:textColor="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tfl_add_space_edit_space" />
해당 버튼은 TextField의 텍스트 값에 따라서 enabled를 처리할 수 있도록 했고, 그에 따른 버튼의 색깔도 처리했습니다.
코드를 팀원들에게 설명하기 위해서 BindingAdapter와 여러 파일을 오가면서 설명했던 기억이 있습니다 ㅎㅎ,,
하지만 Compose를 사용하면 다음과 같이 사용가능합니다.
Button(
onClick = {
createBoard(uiState.boardThumbnailFile, uiState.boardName)
closeDialog()
},
enabled = uiState.boardName.length in 1..20,
modifier = Modifier.width(264.dp),
colors =
ButtonDefaults.buttonColors(
disabledContainerColor = Color.LightGray,
),
) {
Text(text = stringResource(id = R.string.check_message))
}
개인적으로 정말 가독성이 늘어났다고 생각하는데 저만 그런걸지 모르겠네요 ㅎ
그 외에도 XML 방식과 Compose의 방식은 취향차이지만, 직관성은 확실히 Compose가 더 좋은것 같습니다.
RecyclerView의 간편함
정말 크게 느꼈던 점이었습니다. 주변에서 Compose로 RecyclerView를 사용하면 신세계라는 얘기를 많이 들었지만,
실제로 사용해보니 정말 편했습니다.
기존에는 Adpater와 ViewHolder를 통해서 리사이클러뷰를 구현했고,
개인적으로는 아이템의 클릭이벤트들도 클릭리스너를 주입하는등 어려움이 많았습니다.
하지만 Compose는 하나의 아이템뷰를 Composable하게 만들어서 해당 뷰를 쌓아가는 과정입니다.
재사용성이 뛰어나다는점이 정말 와닿았습니다.
코드를 통해서 살펴보면 알 수 있습니다.
Adpater
package boostcamp.and07.mindsync.ui.boardlist
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import boostcamp.and07.mindsync.data.model.Board
import boostcamp.and07.mindsync.databinding.ItemBoardBinding
class BoardListAdapter : ListAdapter<Board, BoardListAdapter.BoardListViewHolder>(DIFF_CALLBACK) {
private var boardClickListener: BoardClickListener? = null
fun setBoardClickListener(listener: BoardClickListener) {
this.boardClickListener = listener
}
class BoardListViewHolder(
private val binding: ItemBoardBinding,
private val boardClickListener: BoardClickListener?,
) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Board) {
with(binding) {
board = item
cbBoard.isChecked = item.isChecked
imgbtnBoardItem.setOnClickListener {
boardClickListener?.onClick(item)
}
cbBoard.setOnClickListener {
item.isChecked = !item.isChecked
boardClickListener?.onCheckBoxClick(item)
}
}
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
): BoardListViewHolder {
val binding = ItemBoardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BoardListViewHolder(binding, boardClickListener)
}
override fun onBindViewHolder(
holder: BoardListViewHolder,
position: Int,
) {
holder.bind(getItem(position))
}
companion object {
val DIFF_CALLBACK =
object : DiffUtil.ItemCallback<Board>() {
override fun areItemsTheSame(
oldItem: Board,
newItem: Board,
): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: Board,
newItem: Board,
): Boolean {
return oldItem == newItem
}
}
}
}
Compose
LazyVerticalGrid(
state = scrollState,
columns = GridCells.Adaptive(minSize = 128.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
contentPadding = PaddingValues(20.dp),
content = {
items(
items = uiState.boards,
itemContent = {
BoardRow(
board = it,
onCheckBoxClicked = onCheckBoxClicked,
navigateToMindMap = navigateToMindMap,
)
},
)
},
)
두 코드 모드 뷰를 꾸미는것이 아닌 리사이클러뷰 형태를 구현하는 코드입니다.
뷰에 대한 아이템을 어떻게 할지는 어뎁터는 XML에, compose는 다른 Composable 함수로 구현하고 있는것이 그 차이입니다.
안드로이드를 하면서 자주 사용되는 RecyclerView를 구현하기 위해서 매번 Adpater와 ViewHolder를 작성하는 코드의 양은 무시하지 못할 정도이고, Compose를 통해서 간단하게 할 수 있는 것을 느꼈습니다.
편리하지 않나요,, ㅎㅎ
그 외에도 Dialog, Navigation 사용도 편했습니다.
힘든점
Compose를 하면서 좋은점도 있었지만 힘든점도 있었습니다..ㅎ
레퍼런스 부족
제가 못찾은것도 있지만, Compose를 검색어에 포함시키면 정말 자료가 현저히 적었습니다
따라서 공식문서와 공식문서의 깃헙 레포지토리를 참고해가면서 했고, 이건 조금 힘들었습니다ㅎ,,
상태 관리
Compose의 생명주기에 따라 Recomposition을 일으키는 상태(UiState)를 관리가 힘들다고 느꼈습니다.
자칫 잘못된 코드는 계속해서 Recompose를 불렀고, 이는 화면 버벅거림등 큰 문제를 발생시켰습니다.
이래서 MVI 패턴과 Compose가 궁합이 좋다는 얘기가 있나..
다음에 기회가 된다면 MVI와 Compose를 이용해서 프로젝트를 해보고 싶네요!
느낀점
Compose가 간결하고 편리하지만 러닝커브가 결코 XML에 비해서 쉽지는 않은것 같다고 느꼈습니다.
그 외에도 Compose의 재사용성을 강력하게 이용하기 위해서 노력은 해봤지만,
아직까지 제가 만든 Composable을 다른 View에서 사용하기 수월할 정도로 개발하지는 못했습니다,, ㅠ
일회성 이벤트 처리인 화면전환이나 토스트,스낵바와 같은 처리도 SideEffect로 처리를 해야하지만
MVI 패턴을 아직 딥하게 학습하지 못해서 아쉬웠습니다.
그럼에도 개발 생산성 향상, 직관적인 코드, 순수 코틀린을 이용한다는 점은 충분히 장점으로 다가왔고,
느낀 어려운점이나 미숙한 점은 충분히 학습을 하면 되는 문제라고 생각이 드네요
아직은 Compose 어린이지만,, 어른이 되는 그날까지.
'Experience > 후기(코딩테스트,프로그램,프로젝트)' 카테고리의 다른 글
SSAFY 11기 수료 후기 (4) | 2025.01.05 |
---|---|
부스트캠프 웹 ﹒모바일 8기 멤버십 수료 후기 (4) | 2023.12.25 |
부스트캠프 웹・모바일 8기 챌린지 수료 후기 (9) | 2023.08.07 |
2023 부스트캠프 웹 ﹒ 모바일 8기 지원 및 합격 후기 (3) | 2023.07.05 |
[어플리케이션 개발] - 연구실 프로젝트 리뷰 (1) | 2023.07.05 |