안드로이드 프로그래밍/jetpack compose

핵심만 골라 배우는 젯팩 컴포즈 (chapter 18 ~20)

1chanhue1 2025. 5. 8. 18:02

컴포즈 개요

  • 컴포즈는 앱 개발에 관한 완전히 새로운 접근 방식.
  • 컴포즈는 선언적인 동시에 데이터 주도적.

컴포즈의 선언적 구문

  • 프로그래머가 선언만 하면 레이아웃 배치, 제한, 렌더링 방법에 관한 모든 복잡한 세부 사항은 컴포즈가 자동으로 처리.
  • 컴포즈의 선언은 계층적으로 구조화되어 있기 때문에, 재사용 가능한 서브 뷰를 조합함으로써 복잡한 뷰를 쉽게 만들 수 있음.

명령형 프로그래밍 패러다임 (기존 안드로이드 XML + Java/Kotlin)

  • 기존의 명령형 패러다임은 트리형태로 뷰를 구성. 레이아웃 위에 텍스트 뷰, 버튼 등을 쌓아 뷰를 그림.
  • UI를 업데이트 하기 위해 일반적으로 findViewById() 같은 함수로 트리를 탐색해 내부 상태를 변경함.
  • xml을 inflating 하여 객체를 만들어 사용, 위젯들은 내부에 각각 상태를 가짐. 상태에 접근할 수 있는 게터, 세터 제공.
  • 수동으로 작업하는 기존 방법은 오류가 발생할 가능성 큼.

선언형 프로그래밍 패러다임 (Jetpack Compose)

  • 선언형 UI 모델은 필요한 변경사항만 적용하는 방식.
  • 특정 시점에 UI의 어떤 부분을 다시 그려야 하는지를 지능적으로 선택.
  • Compose compiler가 어떤 시점에 어떤 부분을 그려야 하는지 판단해 업데이트 함.
  • 게터, 세터 존재하지 않음. 동일한 composable function을 다른 param으로 호출해 UI를 변경.
var name by remember { mutableStateOf("World") }

Text("Hello $name")

 

  • name 값이 바뀌면, Text("Hello $name")는 자동으로 다시 그려짐
  • findViewById()도 필요 없고, 뷰를 직접 탐색할 필요도 없음
  • 어떤 UI를 바꿔야 할지 Compose가 판단함 (최소 변경만 적용)

 

컴포즈는 데이터 주도적

  • 컴포즈는 상태(state) 기반 시스템으로 데이터를 상태로 저장해 데이터의 변경을 감지하기 위한 코드를 추가로 작성하지 않아도, 변경사항이 사용자 인터페이스에 자동으로 반영.
  • 상태가 변경되면 해당 데이터를 구독==(감시)하는 컴포넌트가 삭제되고 새로운 컴포넌트가 생성되어 상태를 반영, 이 과정을 recomposition이라 함.
  • 컴포즈는 데이터 변경에 기반해 앱의 동작과 형태를 결정한다는 점에서 데이터 주도적이라 함. state recomposition을 통해 이를 달성.

컴포저블 함수란 무엇인가?

  • 컴포저블 함수(컴포저블, 컴포넌트 라고도 부름)는 컴포즈로 사용자 인터페이스를 만들기 위해 이용하는 특수한 코틀린 함수.
  • @Composable 어노테이션을 이용해 선언, 코틀린의 일반 함수와 구별됨.
  • 본질적으로 컴포저블 함수는 데이터를 사용자 인터페이스 요소로 변환.
  • 컴포즈 런타임으로 사용자 인터페이스 요소를 전달하면 컴포즈 런타임을 통해 렌더링(그린다는 것).
  • 분리 및 재사용을 위해 가능한 최소한의 정보를 포함해야함.(UI를 만드는 코드가 너무 크면 재사용이 어렵기 때문에,작은 단위로 쪼개서 여러 컴포저블 함수로 만들라는 뜻)

상태(stateful) 컴포저블과 비상태(stateless) 컴포저블

  • 상태란 앱 실행 중 변경할 수 있는 모든 값. ex) 텍스트 필드에 입력된 문자열, 체크박스의 설정 상태 
  • 컴포저블 함수는 상탯값 저장 여부에 따라 상태 컴포저블 비상태 컴포저블로 분류.
  •  
  • 상태를 저장(기억)하려면 remember 키워드를 이용하고 mutableStateOf 함수 호출.
  • remember는 객체를 컴포지션에 저장, remember를 호출한 컴포저블이 컴포지션에서 삭제되면 그 객체를 삭제.
  • mutableStateOf는 컴포즈에서 관찰 가능한 MutableState를 만듬. 관련 값이 변경되면 그 값을 읽는 컴포저블 함수의 재구성이 예약됨.
  • 스스로 상탯값을 저장하지 않는다면 비상태 컴포저블.
// 비상태 컴포저블 (값만 받아서 UI 그림)
@Composable
fun Greeting(name: String) {
    Text("Hello, $name!")
}

// 상태 컴포저블 (자기 안에서 상태를 만듦)
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

파운데이션 컴포저블과 머티리얼 컴포저블

  • 컴포즈에서 번들로 제공하는 컴포저블은 레이아웃, 파운데이션, 머터리얼 디자인 이 세 가지로 분류.
  • 레이아웃 컴포넌트 : 컴포넌트를 화면에 배치, 상호 동작하는 방법을 정의.
    • Box, ConstaintLayout, BoxWithConstraints, Row, Column
  • 파운데이션 컴포넌트 : 기본적인 사용자 인터페이스 기능을 제공하는 최소한의 컴포넌트 집합, 기본적으로 특정한 스타일이나 테마를 내포하진 않지만, 커스터마이즈를 통해 앱의 형태나 행동을 자유롭게 정의할 수 있음. 
    • BaseTextField, LazyRow, Canvas, Shape, Text, Image, LazyColumn
    머티리얼 디자인 컴포넌트 : 구글이 제공하는 머터리얼 테마 가이드라인을 만족하도록 특별히 디자인 된 것.
    • AlertDialog, RadioButton, Button, Card, CheckBox, Snackbar,Scaffold,TextField ....등 여러가지

컴포넌트를 선택할 때는 파운데이션 컴포넌트와 머티리얼 디자인 컴포넌트가 상호 배타적이지 않는다는 점(= 함께 써도 된다, 예: LazyColumn(Foundation) 안에 Card(Material)를 넣을 수 있음)을 염두에 두어야한다. (적절히 섞어 쓰자)

머티리얼 디자인 카테고리에는 그에 상응하는 파운데이션 컴포넌트가 없고, 그 반대의 경우도 마찬가지이므로 두 카테고리의 컴포넌트를 함게 이용해 디자인하게 될 것임.

컴포즈 상태(state)

  • 컴포저블 함수에서 상태 변수에 할당된 값은 기억되어야 함.
  • 상태를 포함한 컴포저블 함수를 호출할 때마다 지난번에 호출했을 때의 상탯값을 기억해야 함.
  • 상태 변수의 변경은 사용자 인터페이스를 구성하는 컴포저블 함수 계층 트리 전체에 영향을 미침.

컴포즈 재구성(recomposition)

컴포저블 함수는 데이터를 받고, 해당 데이터를 이용해 사용자 인터페이스 영역을 만든다. 컴포즈 런타임은 이 요소들을 랜더링(그린다)한다. 한 컴포저블 함수에서 다른 함수로 전달된 데이터는 대부분 부모 함수에서 상태로서 선언된다. 이는 부모 컴포저블의 상탯값 변화가 모든 자식 컴포저블에 반영되며, 해당 상태가 전달된다는 것을 의미한다. 이는 부모 컴포저블의 상탯값 변화가 모든 자식 컴포저블에 반영되며, 해당 상태가 전달된다는 것을 의미한다. 콤포즈에서는 이를 recomposition이라는 동작으로 실행한다. 

  • recomposition은 컴포저블 함수의 계층 안에서 상탯값이 변경될 때 일어남.
  • recomposition이란 간단히 말하면, 해당 함수들을 다시 호출하고, 새로운 상탯값을 전달하는 것.
  • 상탯값이 변경될 때마다 사용자 인터페이스의 전체 컴포저블 트리를 재구성하는 것은 사용자 인터페이스의 랜더링과 업데이트에 있어 매우 비효율적.
  • 컴포즈는 해당 상태 변화에 직접 영향을 받는 함수들만 재구성하는 지능적 재구성 기법을 이용해 오버헤드를 피한다. 즉, 해당 상탯값을 읽는 함수들만 recomposition 하는 것 
@Composable
fun CounterApp() {
    var count by remember { mutableStateOf(0) }

    Column {
        Text("Count is $count") // 이 줄만 상태를 읽음 → 여기가 recomposition 대상!
        Button(onClick = { count++ }) {
            Text("Increase")
        }
    }
}

1. 앱이 처음 실행될 때 CounterApp()이 호출되면서 화면이 그려짐

2.사용자가 버튼을 누르면 count 값이 증가 → mutableStateOf가 상태 변경 감지

3. Compose는 "이 상태(count)를 사용하는 Text 부분"만 다시 그림 (Text("Count is $count"))

4. Button과 Column은 그대로 유지됨

 

단방향 데이터 흐름(Unidirectional Data Flow, UDF)

단방향 데이터 흐름이란?

UI 상태(State)는 위(부모)에서 아래(자식)로 흐르고, 사용자 이벤트는 아래(자식)에서 위(부모)로 전달되는 구조를 의미

즉, 데이터는 내려감 (상태는 부모가 소유, 자식은 표시만 함), 이벤트는 올라감 (자식이 상태를 직접 바꾸는 게 아니라 부모에게 알려줌)

 

단방향 데이터 흐름 방식

@Composable
fun MyScreen() {
    var name by remember { mutableStateOf("") }

    NameInput(
        name = name,
        onNameChange = { name = it }
    )
}

@Composable
fun NameInput(name: String, onNameChange: (String) -> Unit) {
    TextField(
        value = name,
        onValueChange = onNameChange
    )
}

 

흐름 설명 

  • MyScreen은 상태 name을 가짐 (데이터를 소유)
  • NameInput에 name과 onNameChange를 전달함
  • 사용자가 입력하면 onNameChange를 통해 이벤트가 부모로 올라감
  • 부모가 상태를 업데이트함 → UI가 다시 그림 (recomposition)

정리

  • 컴포저블은 상태이벤트를 기반으로 작동
  • 한 컴포저블에 저장된 상태자식 컴포저블 함수들에 의해 직접 변경되어서는 안됨.
  • 데이터는 컴포저블 계층을 따라 전달되며, 이벤트는 계층의 반대 방향인 조상 컴포넌트의 핸들러로 호출됨

상태 호이스팅

  • 상태를 자식 컴포저블에서 이를 호출한 부모 컴포저블로 들어 올림.
  • 상태를 부모 함수로 들어 올림으로써 재사용성 용이.
  • 함수에 상태를 추가할 때는 호출자에게 상태를 호이스팅 가능한지를 고려하자.

 

@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
    Button(onClick = onIncrement) {
        Text("Count: $count")
    }
}

@Composable
fun CounterScreen() {
    var count by remember { mutableStateOf(0) }

    // 상태: count (부모가 소유)
    // 데이터 전달: count → Counter
    // 이벤트 처리: onIncrement → count++
    Counter(count = count, onIncrement = { count++ })
}
  • CounterScreen은 상태를 갖고 있고, Counter는 상태를 보여주고, 버튼 클릭 이벤트를 부모에게 전달 , 즉 상태는 부모가 소유 자식은 이벤트만 알림 -> Compose 알아서 뷰 개선 

환경 설정 변경을 통한 상태 저장

  • remember 키워드는 configuration change(환경 설정 변경)에서 상태를 유지하지 못함. 
  • 환경 설정 변경은 일반적으로 기기의 변경에 의해 일어나며 액티비티 형태를 바꾼다(예 : 기기 방향 변경이나 시스템 전체 폰트 설정 변경 등 )
  • remeberSaveable을 이용해 유지.

 

 

 

 

출처: 핵심만 골라 배우는 젯팩 컴포즈 (닐 스미스)

참고: https://yoon-dailylife.tistory.com/128 [알면 쓸모있는 개발 지식:티스토리]