명령형 UI와 선언형 UI의 차이

SSY·2023년 2월 25일
4

Compose

목록 보기
1/15
post-thumbnail

1. 시작하며

xml기반의 ViewSystem에서 kotlin기반의 Compose로 전환하며 패러다임이 바뀌었는데, 이는 '명령형'에서 '선언형'으로의 변화를 의미한다. 이로인해 개발자는 UI를 업데이트할 때, 명령적인 메서드 호출(eg., TextView.setText등)을 전혀 신경쓰지 않아도 되며, 오로지 Composable함수만 호출 및 그 파라미터에 상태값을 선택적으로 주입함으로써 UI 바인딩이 더 쉬워진다. 이로인해 라인수도 크게 줄어든다. 단적인 예로, ViewSystem의 RecyclerView를 사용할 때와 비교했을 때, Compose는 Lazy*메서드를 사용하여 라인수를 크게 줄일 수 있다.

[Compose의 이점]

  • 라인 수의 감소.
  • 코드의 재사용성 향상.

2. 명령형UI와 선언형 UI

2.1. 명령형UI

명령형 프로그래밍 방식이란, 위에서 말했듯, 특정 작업을 수행할때, 문자 그대로 명령을 내려 작업을 진행시키는 것을 의미한다. 이때의 명령은 함수 호출을 통한 동작 지시를 의미한다. 예를 들어, 우리가 TextView에 데이터를 쓴다고 가정해보자. 이땐 다음과 같은 방식이 들어간다.

<TextView
	android:id="+@id/tvTextView"
	android:text="helloWorld"
	...
/>

위 TextView는 Android에서 제공해주는 UI 갱신 API인데, 아래와 같이 class의 형태를 띄고있으며, 그 내부에서 UI갱신을 위한 메서드 또한 다수 제공한다.

@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 
{ ... }

즉, 우리가 ViewSystem에서 TextView의 문자를 변경해주는 등의 UI 업데이트 작업은 android:text="helloWorld와 같이, 클래스 내부 메서드를 호출해주게 된다.

    public final void setText(CharSequence text) {
        setText(text, mBufferType);
    }

참고 : [Android공식 홈페이지의 선언형 패러다임 부분]

[정리]
ViewSystem은 xml기반, 명령형 방식, 즉 메서드 호출 방식으로 UI를 업데이트하며, 이를 위해 개발자는 TextView.setText등의 메서드 호출을 해줘야한다.

2.2. 선언형UI

선언형 방식의 COmpose는 UI 업데이트를 어떻게 하는지 알아보자.

@Composable
fun loadTextView() {
	Text("HelloWorld")
}

매우 간결하다. TextView에 대응되는 Text에 문자값 설정을 위해 위 샘플 코드와 같이 '상태값을 주입'해주기만하면 된다. 이는 기존 TextView와 비교해봤을 때, 라인수가 크게 줄었다는 것을 알 수 있다. 기존 ViewSystem의 TextView는 UI 갱신을 위해 findViewById<TextView>()DataBinding등의 방식으로 UI참조 후, 명령형 방식으로 갱신해주었다. 하지만 선언형 기반 Compose는 위와같이 단순 '상태값 주입'만으로 끝이다.

추가적으로, ViewSystem의 RecyclerView를 대체한 LazyColumn()도 라인수를 매우 크게 줄인 1가지 경우이다.

우린 Compose이전, RecyclerView를 어떻게 사용했는지 복기해보자. 사용을 위한 첫 단계로, RecyclerView.Adapter를 정의한다. 그리고 각 아이템에 맞는 xml또한 정의해주어야 한다. 어댑터 정의가 끝났으면, Activity내, Adapter를 Activity에 바인딩되는 코드 또한 작성해줘야만 한다.

// RecyclerView.Adapter 정의
class MainAdapter: RecyclerView.Adapter<MainAdapter.MainViewHolder>() {
    override fun onCreateViewHolder() { ... }
    override fun onBindeViewHolder() { ... }
    override fun getItemCount() { ... }
    inner class MainViewHolder() { ... }
}
// RecyclerView.Adapter를 Activity에 연결
binding {
    adapter = MainAdapter()
    layoutManager = ...
    setHasFixedSize(true)
}

이러한 일련의 여럿 작업의 결과, 1개RecyclerView의 라인수는 200줄은 쉽게 넘어간다. 하지만 Compose에선 어떨까?

@Composable
fun MainListView() {
	LazyColumn() {
    	    items(items = item) { item ->
        	    ItemView(item = item)
        	}
    	}
	}
}

@Composable
fun ItemView() {
    Text("HelloWorld")
}

이로인해 알 수 있는 점은 Compose는 '선언형 UI'방식을 채택하기에, 단순 상태값(안드로이드 앱 아키텍처에선 UiState)변경만으로 이를 Composeble함수 파라미터에 주입하고 이로 인해 UI가 상태값에 '반응'하여 변경된다. 또한 당연하게도, '명령형 UI'방식의 단점인 'UI 갱신을 위한 메서드 호출'작업이 없어, 라인 수가 크게 줄어든 것을 확인할 수 있으며, 개발자가 이를 신경쓰지 않아도 되어 개발 유지보수성 또한 올라간다.

참고 : [안드로이드 공식 홈페이지]

추가

Compose를 처음 접하는 사람은 Compose UI 컴포넌트인 TextView("HelloWorld")가 객체라고 생각할 수 있다. 그도 그럴 것이, 맨 앞글자가 '대문자'이기 때문이다. 하지만 이는 Composable메서드를 선언하기 위한 컨벤일 뿐이다. 사실상 이는 함수이다.

package androidx.compose.material3

@Composable
fun Text(
    text: String,
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    fontSize: TextUnit = TextUnit.Unspecified,
    fontStyle: FontStyle? = null,
    fontWeight: FontWeight? = null,
    fontFamily: FontFamily? = null,
    letterSpacing: TextUnit = TextUnit.Unspecified,
    textDecoration: TextDecoration? = null,
    textAlign: TextAlign? = null,
    lineHeight: TextUnit = TextUnit.Unspecified,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    onTextLayout: (TextLayoutResult) -> Unit = {},
    style: TextStyle = LocalTextStyle.current
) { ... }

위 TextView메서드를 클릭해서 타고 들어간 TextView의 모습이다. 보면 놀랍게도 함수라는 것을 알 수 있으며, 내부 파라미터인 color, fontSize, text등을 받고있는걸 볼 수 있다. 이로 인해 Text로 인해 표현되는 UI를 업데이트할 수 있는 것이다.

참고 : [Android 공식 홈페이지]

명령형 UI와 선언형 UI를 정리하면 아래와 같다.

  • 명령형 UI는 업데이트를 위해 메서드의 호출(eg., TextView.setText) 즉, 말 그대로 명령적 방식으로 UI를 업데이트한다.
  • 선언형UI 방식은 상태값(eg., 안드로이드 앱 아키텍처 패턴 or MVI패턴에서 상태값을 들고 있는 ViewModel내, UiState 객체)을 옵저빙함으로써 해당 상태값이 업데이트를 자동 수신 가능하며, 이로 인해 선언형 UI의 함수에 선언 된 파라미터에 변경된 값들이 자동 주입된다. 즉, 개발자는 UI 업데이트를 위해 명령적인 방식으로 메서드를 호출하는게 아니라, 단순 상태값 업데이만 신경쓰면 된다.
  • 선언형 UI는 명령적 방식으로 UI를 업데이트하지 않기에, 라인수를 크게 줄일 수 있다. 또한 개발자는 명령적 업데이트 방식을 신경쓰지 않아도 되므로 유지보수성 또한 올라간다.
profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.

0개의 댓글