선언형 UI에 대한 나의생각
태초의 개발
안드로이드를 개발하면서 XML을 이용하여 UI를 작성하는데 가장 귀찮은 점이 기본 Material을 벗어나서 Custom UI를 개발할 때다.
작은 UI 쪼가리를 개발하기 위해 많은 시간과 코드를 소비하는데, 정말 작은 기능인데도 불구하고 작게는 30분 길게는 하루를 UI에 시간을 소비했던 적이 있다. 특히 ToggleGroup과 Checkbox는 귀찮은 작업이다. 또 동일한 로직을 가지고 있는 화면에서 사용하기 위해 Fragment를 사용하는데 파일이 또 늘어나고, 처리해야 하는 코드도 동시에 늘어난다. 눈앞이 캄캄해진다.
이런 경험을 하면서 "이게 정말 효율적인 방법일까?"라는 의문이 들었다. UI 개발은 반복적인 작업이 많은데, 매번 비슷한 코드를 작성하고, 작은 변경사항에도 많은 코드를 수정해야 하는 것이 답답했다. 특히 디자이너와 협업할 때는 더욱 그랬다. 디자이너가 "이 부분만 조금만 더 왼쪽으로"라고 요청하면 XML 파일을 열고, 속성을 찾고, 값을 수정하는 과정이 반복되었다.
선언형 UI의 등장
그러다가 선언형 UI라는 개념을 접하게 되었다. 처음에는 그저 트렌드로만 생각했는데, 실제로 사용해보니 개발 방식이 완전히 달라졌다. 명령형(Imperative) UI에서는 "어떻게 그려야 하는가"에 집중했다면, 선언형 UI에서는 "무엇을 그려야 하는가"에 집중하게 된다.
이것은 마치 건축가가 집을 지을 때, 명령형은 "벽돌을 하나씩 쌓아올리고, 시멘트를 바르고..."라고 하는 것이라면, 선언형은 "이 집은 3개의 침실과 2개의 화장실이 있는 2층 건물이다"라고 하는 것과 비슷하다. 결과물은 같지만, 접근 방식이 완전히 다르다.
Jetpack Compose 사용
Flutter를 쓰기 전에는 안드로이드의 Jetpack Compose를 먼저 사용해 보았다. 상태와 함께 무엇을 렌더링할 것인가만 생각하면 작업하기 쉬웠기 때문에 금방 적응을 했다. 하지만, 느리다. 안 그래도 느린 맥북과 안드로이드 스튜디오가 내 혈압을 상승시키는데 Compose의 장점이라고 할 수 있는 Preview가 코드를 한번 작성(수정)할 때마다 끊기기 시작하니 참을 수 없는 분노가 찾아왔다.
그래도 한 번 작성해둔 UI를 다른 곳에서 쉽게 사용하고, 눈에 보이는 UI 코드를 작성할 수 있어서 좋았다. 특히 재사용 가능한 컴포넌트를 만드는 것이 훨씬 쉬워졌다. 기존에는 XML로 작성한 레이아웃을 재사용하려면 복잡한 과정이 필요했지만, Compose에서는 단순히 함수를 호출하는 것만으로도 가능했다.
몇 번 사용해 보니 좋았는데 다시 옛날 프로젝트를 봐야하는 상황이 찾아왔었다. 이럴 수가 Compose를 그리워할 줄은 몰랐다.
임시방편으로 아래처럼 코드를 작성하기 시작했다.
addLinearLayoutVertical {
addCheckBox("동의합니까?") {
setOnCheckedChangeListener { button, isChecked ->
// 체크 처리
}
}
}
그렇다. 모든 UI들을 코드를 이용하여 작성하기 시작했다. Kotlin으로 작성했기 때문에서 Compose처럼은 아니지만 머릿속에서 그리면서 작성할 수 있었다. 직관적이고 빨랐다. 스스로 생산성이 올랐다고 칭찬했다. 하지만 미리볼 수 없었기 때문에 확인을 위해서 실제 스마트폰을 이용하여 확인할 수밖에 없었다.
하나의 프로젝트를 이런 식으로 코드만 작성해서 처리해 버린 적도 있다. 이 경험은 나에게 중요한 교훈을 주었다. 선언형 UI의 핵심은 "무엇을 그릴 것인가"에 집중하는 것이지만, 그것을 구현하는 방식은 다양할 수 있다는 점이다. XML이든 코드로 직접 작성하든, 핵심은 UI의 의도를 명확하게 표현하는 것이다.
Flutter
사이드 프로젝트를 시작했다. 이왕 시작하는 거 그냥 iOS까지 배포하고 싶었다. 그런데 iOS를 안 한 지 조금 오래되어서 다시 시작하려니 약속하지 못할 것 같아서 크로스 플랫폼인 Flutter를 선택했다.
Compose와 크게 다르지 않아서 어떤 식으로 작성하는지 어떻게 동작하는지만 파악하고, 바로 작업에 들어갔다. 뭐든 해보면 된다.
가장 큰 장점은 빠르다. 기존에 작업하는 방식과는 비교할 수 없을 만큼 빠르고, UI를 구현함에 있어서 개발자가 신경 써야 할 게 많이 줄어들었다. 상태관리만 잘 해주면 의도대로 작동하는 것이다. UI의 일부를 변경하고, 제거하고, 추가하는데 많은 비용이 들지 않는다.
개인적으로 Jetpack Compose와 Flutter를 많이 써보진 않았지만 Flutter가 훨씬 낫다. 답답함도 별로 없고, HotReload 기능을 가지고 있기 때문에 바로 확인할 수 있다. 네이티브 기능을 위해서 MethodChannel을 이용해야 하는 것은 사소하게만 느껴질 뿐이다.
선언형 UI의 장단점
선언형 UI를 사용하면서 느낀 장점은 다음과 같다:
- 생산성 향상: UI를 작성하는 속도가 빨라졌다. 특히 반복적인 UI 요소를 만들 때 더욱 그렇다.
- 코드 가독성: UI가 어떻게 생겼는지 코드만 보고도 직관적으로 이해할 수 있다.
- 재사용성: 컴포넌트를 쉽게 재사용할 수 있어 중복 코드가 줄어든다.
- 상태 관리: UI와 상태가 명확하게 분리되어 있어 상태 관리가 용이하다.
하지만 단점도 있다:
- 학습 곡선: 기존 방식에 익숙한 개발자에게는 새로운 패러다임을 익히는 데 시간이 걸린다.
- 성능 이슈: 특히 복잡한 UI에서는 성능 이슈가 발생할 수 있다.
- 디버깅의 어려움: 선언형 UI에서는 UI의 상태가 여러 곳에 분산되어 있어 디버깅이 어려울 수 있다.
결론
선언형 UI는 단순한 트렌드가 아니라 UI 개발 방식의 패러다임 전환이라고 생각한다. 처음에는 적응하기 어려울 수 있지만, 한번 익숙해지면 이전 방식으로 돌아가고 싶지 않을 정도로 강력한 도구다.
특히 Flutter와 같은 크로스 플랫폼 프레임워크에서는 선언형 UI가 필수적이라고 생각한다. 하나의 코드베이스로 여러 플랫폼을 지원하려면 UI를 추상화하고 선언적으로 표현하는 것이 가장 효과적인 방법이다.
앞으로도 선언형 UI는 계속 발전할 것이고, 더 많은 개발자들이 이 방식으로 UI를 개발하게 될 것이다. 나도 계속해서 이 방식을 사용하면서 더 나은 사용자 경험을 제공하는 앱을 만들고 싶다.