본문 바로가기

Compose

[Compose] Compose 아키텍처

 

Compose UI는 Immutable하고 직접 업데이트할 수 없다

UI 상태를 통해 Compose가 변경된 부분만 다시 그린다

 

또한 Composable 함수는 기본적으로 상태는 아래로 흐르고 이벤트는 위로 흐른다

예시) 상태를 입력으로 받아(name) 이벤트를 노출하는(onValueChange) Composable

var name by remember { mutableStateOf("") }
OutlinedTextField(
    value = name,
    onValueChange = { name = it },
    label = { Text("Name") }
)

 

Compose에는 단방향 데이터 흐름이 적합하다

 

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

상태는 아래로 이동하고 이벤트는 위로 이동하는 디자인 패턴

UDF 구조

 

Compose에서 UDF가 동작하는 방식

상태는 State 객체에 저장된다 (mutableStateOf 함수 사용)

상황에 따라 remember, rememberSaveable을 사용해 컴포지션 내에서 상태 유지

상태는 아래로 흘러 각 Composable에서 필요한 상태를 입력으로 받는다

상태가 변경되면 상태를 사용하는 Composable 함수들이 다시 그려진다

 

이벤트(사용자 입력, 네트워크 요청 등)는 위로 흘러 ViewModel에서 이벤트를 처리한다

이벤트 처리 후 상태가 변경되면, 상태는 다시 아래로 흘러 UI 업데이트

 

Compose에서 UDF 사용 시 장점

테스트 용이 - 각 Composable을 독립적으로 테스트 가능

유지 보수 향상 - 코드가 간결하고 수정하기 쉽다

UI 일관성 향상 - 상태가 변경되면 UI가 변경되는 일관성

 

 

Composable 매개변수 정의

 

매개변수를 정의할 때 고려할 사항

Composable 재사용 가능성

상태 매개변수가 Composable 성능에 미치는 영향

 

각 Composable에는 최소한의 정보만 가지는 것이 좋다

@Composable
fun Header(title: String, subtitle: String) {
    // title, subtitle 변경 시 recompose
}

@Composable
fun Header(news: News) {
    // 새로운 News 전달 시 recompose
}

 

 

Compose 이벤트와 상태관리

이벤트는 UI 상태를 변경시키고 ViewModel은 이를 처리하여 UI 상태 업데이트

중요한 점은, UI Layer는 이벤트 핸들러 외부에서 상태를 변경하지 않아야 한다

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

    // UI 레이어에서 상태를 직접 변경. 버그 유발 가능
    // count++

    Column {
        Text("Count: $count")
        Button(onClick = {
            // 이벤트 핸들러를 통해 상태 변경
            count++
        }) {
            Text("Increment")
        }
    }
}

 

 

또한 Immutable 값을 상태, 이벤트 핸들러 람다에 전달하는 것이 좋다

이로 인해

Composable 재사용으로 인해 재사용성 향상

UI가 상태 값을 직접 변경하지 않음

상태가 다른 스레드에서 변경되지 않아 동시 실행 문제가 없다

의 이점이 있다

@Composable
fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) {
    TopAppBar(
        title = {
            Text(
                text = topAppBarText,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .wrapContentSize(Alignment.Center)
            )
        },
        navigationIcon = {
            IconButton(onClick = onBackPressed) {
                Icon(
                    Icons.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

 

 

또한 ViewModel과 mutableStateOf를 사용하여 앱에 단방향 데이터 흐름을 도입할 수 있다

- UI 상태는 관찰 가능한 상태 홀더 (StateFlow, LiveData)를 통해 노출

- ViewModel은 이벤트를 처리하고, 이벤트를 기반으로 상태 홀더를 업데이트

 

예시) 로그인 화면

상태

- SignedOut (로그아웃 상태)

- InProgress (로그인 진행 중)

- Error (오류 발생)

- SignedIn (로그인 완료)

// 위 상태를 sealed class로 모델링
sealed class LoginUiState() {
    object SignedOut : LoginUiState() // 로그아웃 상태
    object InProgress : LoginUiState() // 로그인 진행 중
    data class Error(val message: String) : LoginUiState() // 오류 발생
    object SignedIn : LoginUiState() // 로그인 완료
}


class MyViewModel : ViewModel() {
    private val _uiState = mutableStateOf<LoginUiState>(LoginUiState.SignedOut)
    val uiState: State<LoginUiState>
        get() = _uiState

    // ...
}