state란?
compose는 기본적으로 아래와 같이 동작한다.
state 변경
→ 해당 Composable 함수 다시 실행
→ 화면 다시 그림 (Recomposition)
기본적으로 Compose에서 어떠한 상태 값이 바뀌게 되면 재구성(Recomposition)이 일어나게 된다.
여기서 재구성이란, 말 그대로 재 생성한다는 뜻이다.
예로 들어, a라는 값을 기본으로 가지고 있고 버튼을 누르면 b라는 값으로 변경되는 TextView가 있다고 가정해 보자.
버튼을 누르게 되면 a라는 값이 b라는 값으로 상태 값이 바뀌게 되는데, 이때 재구성이 일어나게 되어 UI를 다시 그리게 된다.
다시 그리게 되면 b라는 값을 가지게 되는게 아니라, 기본 값인 a가 나오게 되어 사용자가 원하는 동작이 이루어지지 않게 된다.
따라서, 재구성이 되었을 때도 값을 저장할 수 있도록 하기 위하여 Compose에서는 remember 키워드를 제공한다.
val a = remember { mutableStateOf(false) }
var b by remember { mutableStateOf("a") }
val (c, d) = remember { mutableStateOf("") }
이와 같이 3가지 방법으로 remember을 선언하여 사용한다.
각 선언 방법을 설명하기 앞서, mutableStateOf 라는 키워드도 처음 접할 것이다.
mutableStateOf에 대한 설명을 확인해보면 다음과 같이 나와있다.
"MutableState 클래스는 Compose에 의해 읽기와 쓰기를 관찰하는 단일 값 보유자입니다."
Compose에서 상태를 저장하고 상태가 변경 되었을 때 재구성하기 위해서는 관찰 가능한 객체를 관찰해야 하는데, MutableState 클래스는 Compose에서 읽기와 쓰기를 관찰하기 위해 만들어진 클래스라고 생각하면 된다.
다시 위의 변수를 확인해보자. a는 아주 기본적으로 사용하는 방식으로, mutableStateOf(~) 에 들어가는 ~는 해당 변수의 Default값이라고 생각하면 된다. 즉, a라는 변수의 기본 값은 false이고 해당 값을 관찰하고 저장할 예정이라 재구성돼도 default 값으로 저장되지 않는다. 라는 뜻이 된다.
b는 by라는 키워드를 사용하여 get,set을 b에 위임하도록 만들어서 사용하는 방법이다.
여기서 주의해야 할 점은, by 키워드를 사용하는 경우 반드시 var로 선언해야 한다는 점이다.(val에는 getter만 있지만 var에는 getter, setter 둘 다 있기 때문에...)
이것에 대해서는 다음 코드를 보면 이해가 좀 더 쉽게 가능하다.
var isExpanded by remember { mutableStateOf(msg.open) }
val temp = remember { mutableStateOf(msg.open) }
temp.value = msg.open
isExpanded = msg.open
isExpanded와 temp의 값을 갱신하는 부분을 확인해 보면 된다.
by 키워드를 통하여 사용한 isExpanded는 해당 변수에 바로 값을 저장하는 반면,
temp의 경우 .value 를 사용하여 값을 갱신하도록 하고 있다.
.value는 mutableStateOf가 들고 있는 "진짜 값"이라고 생각하면 된다.
by 키워드를 사용하여 선언 하였을 때, import 되는 것 없이 오류만 계속 뜬다면 해당 import가 되어있는지 확인하길 바란다.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
마지막으로, 세번째 사용하는 방법은 람다식을 하나 추가하여 사용하는 방법이다.
조금 사용법이 다르기 때문에 설명부터 확인해보도록 하자.

위의 설명을 보면 알 수 있겠지만, 간단하게 말하자면 값이 변경되면 변경된 값을 저장하고 같은 값으로 변경되면 재구성을 하지 않도록 해준다고 한다.
해당 사용법은 TextField를 사용할 때 아주 유용하게 쓰이는데, 예제 코드를 보면서 이해해보도록 하자.
val (c, d) = remember { mutableStateOf("") }
TextField(value = c, onValueChange = d)
TextField에서 값을 입력받으면 value 값이 계속해서 갱신되게 된다.
그렇게 되면 앞서 말한 재구성이 일어나게 되는데, 이미 작성된 텍스트와 새로 입력받은 텍스트를 저장하지 않으면 정상적으로 글을 작성할 수 없게 되므로 입력받은 값을 추가하여 텍스트 값을 저장해주어야 한다.
이때, 텍스트 값을 변경하는 부분이 onValueChange인데 해당 인자에는
onValueChange: (String) -> Unit
이와 같이 람다식이 들어가야 한다.
해당 람다식을 보고 다시 위의 MutableState를 보면 비슷한 부분을 확인할 수 있을 것이다.
즉, 해당 방식을 사용하여 변수를 선언하게 되면, 별도의 람다식을 넣지 않아도 텍스트 필드에 값이 변경되면 변경된 값을 정상적으로 넣어주게 된다.
조금 더 이해를 돕기 위해 다른 사용법으로 람다식을 구현하면 다음과 같게 된다.
var b by remember { mutableStateOf("") }
TextField(value = b, onValueChange = { change -> b = change})
TextField를 사용하여 키보드로부터 입력을 받으면, 변경 된 값을 b에 저장하고 재구성됐을 때 해당 값을 사용하도록 해야 하므로 onValueChange에 작성한 것처럼 람다식을 추가해야 한다.
위의 람다식을 사용하지 않고 사용할 수 있도록 만들어둔 방법이 세 번째 선언 방식으로 가장 간단한 사용 방법인 TextField를 예로 들어 설명을 했지만, 상황에 맞춰서 알맞은 방식으로 선언하여 사용하면 된다.
class MainActivity : ComponentActivity() {
@SuppressLint("InvalidColorHexValue")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Column(Modifier.fillMaxSize()) {
val color = remember {
mutableStateOf(Color.Yellow)
}
//ColorBox { it: Color ->
//color.value = it
//}
ColorBox(
Modifier.weight(1f).fillMaxSize()
) {
color.value = it
}
Box(modifier = Modifier
.background(color.value)
.weight(1f)
.fillMaxSize())
}
}
}
}
@SuppressLint("UnrememberedMutableState")
@Composable
fun ColorBox(modifier: Modifier = Modifier,
updateColor: (Color) -> Unit
) {
Box(modifier = modifier
.background(Color.Red)
.clickable{
updateColor(
Color(
Random.nextFloat(),
Random.nextFloat(),
Random.nextFloat(),
1f
)
)
})
}
위의 박스를 클릭하면 아래의 박스의 색이 바뀌는 로직을 구현한 코드이다.
몰랐던 것부터 정리해보자
Unit은 자바의 void와 비슷하다. “아무 값도 의미 있게 반환하지 않는다” 는 뜻의 타입이다. 즉,
updateColor: (Color) -> Unit
은 Color 하나를 받아서 아무것도 반환하지 않는 함수라는 뜻이다.(사실 반환은 하는데...너무 깊게 들어가니까 이정도로 이해하자..)
color.value = it 은 단순 실행문이기 때문에, Unit자리에 들어갈 수 있다.
it은 “람다 파라미터가 하나일 때 코틀린이 자동으로 만들어주는 이름”이다. 예제 코드를 보며 이해해 보자.
{ x: Int -> x * 2 }
여기서 x는 우리가 직접 지은 파라미터 이름이다. 이걸 it을 써서 짧게 쓴다면?
{ it * 2 }
와...코틀린 지린다 어쨋든 이게 가능한 이유는 람다식의 패러미터가 딱 하나고, 코틀린이 그 파라미터 이름을 it으로 만들어주었기 때문이다.
즉, it을 사용하려면 두 개의 조건을 만족해야 한다.
- 람다 파라미터가 1개
- 우리가 파라미터 이름을 직접 안 썼을 때
이제 메인액티비티 코드에서 다음 부분을 집중적으로 봐 보자.
ColorBox(
modifier = Modifier
.weight(1f)
.fillMaxSize()
) { color ->
color.value = it
}
@Composable
fun ColorBox(
modifier: Modifier = Modifier,
updateColor: (Color) -> Unit
) {
Box(
modifier = modifier
.background(Color.Red)
.clickable {
updateColor(
Color(
Random.nextFloat(),
Random.nextFloat(),
Random.nextFloat(),
1f
)
여기서 it은 ColorBox가 만들어서 넘겨준 Color 객체다.
Random.nextFloat()는
0 이상 1 미만의 랜덤 실수를 만들어주는 함수고,
Compose Color의 RGB 값으로 쓰기 딱 맞다
'안드로이드 스터디' 카테고리의 다른 글
| JetPack Compose 9편 : ConstraintLayout (0) | 2025.12.31 |
|---|---|
| JetPack Compose 7편 : Lists (0) | 2025.12.31 |
| JetPack Compose 5편 : 텍스트 스타일링 (0) | 2025.12.30 |
| JetPack Compose 4편 : 이미지 카드 만들기 (1) | 2025.12.30 |
| JetPack Compose 3편 : Modifier (2) | 2025.12.30 |