StateFlow์ UI State
์ง๋ ๊ธ์์ ๋ฐ์ดํฐ์ ๋ก์ง์ ViewModel๋ก ์ฎ๊ฒผ๋ค. ๊ทผ๋ฐ ๋์ ๋จ๊ฒจ๋ ๋ฌธ์ ๊ฐ ์์๋ค. todoList๋ฅผ ๋ฐ๊นฅ์์ ์ง์ ๊ฑด๋๋ฆด ์ ์๊ณ , "์ง๊ธ ๋ก๋ฉ ์ค์ด๋ค" ๊ฐ์ ์ํ๋ฅผ ํํํ ๋ฐฉ๋ฒ์ด ์๋ค.
๋ฐ๊นฅ์์ ์ง์ ๊ฑด๋๋ฆด ์ ์๋ค๋ ๊ฒ ๋ญ ์๋ฆฌ๋๋ฉด
class TodoViewModel : ViewModel() {
val todoList = mutableStateListOf(
Todo(id = 1, title = "์ฅ๋ณด๊ธฐ")
)
fun addTodo(title: String) {
todoList.add(Todo(id = todoList.size + 1, title = title))
}
}todoList๊ฐ val์ด๊ธด ํ๋ฐ, mutableStateListOf๋ก ๋ง๋ ๋ฆฌ์คํธ ์์ฒด๋ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๊ฐ์ฒด๋ค. ๊ทธ๋์ ์ปดํฌ์ ๋ธ ์ชฝ ์ฝ๋์์ ์ค์๋ก ์ด๋ ๊ฒ ์ธ ์๋ ์๋ค.
// TodoListScreen ์์์, addTodo๋ฅผ ๊ฑฐ์น์ง ์๊ณ
viewModel.todoList.add(Todo(id = 999, title = "๋ชฐ๋ ์ถ๊ฐ"))addTodo ํจ์๋ฅผ ๊ฑฐ์น์ง ์๊ณ ๋ฆฌ์คํธ๋ฅผ ์ง์ ์กฐ์ํด๋ฒ๋ ธ๋ค. ์ง๊ธ์ addTodo๊ฐ ํ๋ ์ผ์ด ๋จ์ํด์ ํฐ ๋ฌธ์ ๊ฐ ์ ๋ณด์ด์ง๋ง, ๋์ค์ "์ถ๊ฐํ ๋๋ง๋ค ์๋ฒ์๋ ์ ์ฅํ๋ค" ๊ฐ์ ๋ก์ง์ด addTodo ์์ ๋ค์ด๊ฐ๋ฉด, ์ด๋ ๊ฒ ์ง์ ๊ฑด๋๋ฆฌ๋ ๊ฒฝ๋ก๋ ๊ทธ ๋ก์ง์ ๋ค ๊ฑด๋๋ฐ๊ฒ ๋๋ค. ViewModel์ ์ฐ๋ ์ด์ ์ค ํ๋๊ฐ "๋ฐ์ดํฐ๋ฅผ ๋ฐ๊พธ๋ ๊ธธ์ ํจ์ ๋ช ๊ฐ๋ก ์ขํ๋๊ธฐ"์ธ๋ฐ, ์ง๊ธ์ ๊ทธ ๊ธธ์ด ๋งํ์์ง ์๋ค.
StateFlow๋ก "์ฝ๊ธฐ๋ง ๋๋ ์ฐฝ"์ ๋ง๋ ๋ค
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
data class TodoUiState(
val todoList: List<Todo> = emptyList(),
val isLoading: Boolean = false
)
class TodoViewModel : ViewModel() {
private val _uiState = MutableStateFlow(
TodoUiState(todoList = listOf(Todo(id = 1, title = "์ฅ๋ณด๊ธฐ")))
)
val uiState: StateFlow<TodoUiState> = _uiState.asStateFlow()
fun addTodo(title: String) {
val newTodo = Todo(id = (_uiState.value.todoList.maxOfOrNull { it.id } ?: 0) + 1, title = title)
_uiState.value = _uiState.value.copy(todoList = _uiState.value.todoList + newTodo)
}
fun toggleDone(id: Int) {
val updated = _uiState.value.todoList.map {
if (it.id == id) it.copy(isDone = !it.isDone) else it
}
_uiState.value = _uiState.value.copy(todoList = updated)
}
fun deleteTodo(id: Int) {
_uiState.value = _uiState.value.copy(
todoList = _uiState.value.todoList.filter { it.id != id }
)
}
}_uiState(์ธ๋์ค์ฝ์ด๋ก ์์ํ๋ ์ชฝ)๋ MutableStateFlow๋ผ์ ๊ฐ์ ๋ฐ๊ฟ ์ ์๊ณ , uiState(๋ฐ๊นฅ์ ๊ณต๊ฐํ๋ ์ชฝ)๋ StateFlow๋ผ์ ์ฝ๊ธฐ๋ง ๋๋ค. addTodo, toggleDone, deleteTodo ์์์๋ง _uiState.value๋ฅผ ๋ฐ๊พธ๊ณ , ๋ฐ๊นฅ(์ปดํฌ์ ๋ธ ์ชฝ)์๋ ์ฝ๊ธฐ ์ ์ฉ์ธ uiState๋ง ๋ณด์ฌ์ค๋ค. ๊ทธ๋์ ์๊น์ฒ๋ผ viewModel.todoList.add(...)๋ก ๋ชฐ๋ ๊ฑด๋๋ฆฌ๋ ๊ฒ ์ ์ด์ ๋ถ๊ฐ๋ฅํด์ง๋ค.
๋ฐ์ดํฐ๋ฅผ ํ๋์ฉ ๋ฐ๊พธ๋ ๋์ , UiState ์ ์ฒด๋ฅผ ์๋ก ๋ง๋ ๋ค
๋์ฌ๊ฒจ๋ณผ ๋ถ๋ถ์ด ํ๋ ๋ ์๋ค. todoList.add(...)์ฒ๋ผ ๊ธฐ์กด ๋ฆฌ์คํธ๋ฅผ ์ง์ ๋ฐ๊พธ์ง ์๊ณ , _uiState.value = _uiState.value.copy(...)๋ก ๋งค๋ฒ ์ TodoUiState ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ํต์งธ๋ก ๊ต์ฒดํ๋ค. StateFlow๋ "๊ฐ์ด ๋ฐ๋์๋์ง"๋ฅผ ๋น๊ตํด์ ๊ตฌ๋
์์๊ฒ ์ ๊ฐ์ ์๋ ค์ฃผ๋๋ฐ, ๋ฆฌ์คํธ ์์ ํญ๋ชฉ์ ์กฐ์ฉํ ๋ฐ๊ฟ๋ฒ๋ฆฌ๋ฉด "์ด๊ฒ ์ด์ ๊ฐ๊ณผ ๋ค๋ฅธ ๊ฐ์ฒด"๋ผ๋ ์ ํธ๊ฐ ๋ช
ํํ์ง ์์ ์ ์๋ค. copy()๋ก ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ๊ต์ฒดํ๋ฉด "๊ฐ์ด ๋ฐ๋์๋ค"๋ ๊ฒ ๋ถ๋ช
ํด์ง๋ค.
์ปดํฌ์ ๋ธ์์๋ collectAsStateWithLifecycle๋ก ๊ตฌ๋ ํ๋ค
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@Composable
fun TodoApp(viewModel: TodoViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "list") {
composable("list") {
TodoListScreen(
todoList = uiState.todoList,
onAdd = viewModel::addTodo,
onToggleDone = viewModel::toggleDone,
onDelete = viewModel::deleteTodo,
onTodoClick = { todo -> navController.navigate("detail/${todo.id}") }
)
}
composable("detail/{todoId}") { backStackEntry ->
val todoId = backStackEntry.arguments?.getString("todoId")?.toIntOrNull()
val todo = uiState.todoList.find { it.id == todoId }
TodoDetailScreen(
todo = todo,
onToggleDone = viewModel::toggleDone,
onBack = { navController.popBackStack() }
)
}
}
}[์ด๋ฏธ์ง: ํ ์ผ์ ์ถ๊ฐยท์ฒดํฌยท์ญ์ ํ๋ ๋ชจ์ต โ StateFlow๋ก ๋ฐ๊พผ ๋ค์๋ ๋์์ ๊ทธ๋๋ก์ธ ํ๋ฉด]
collectAsStateWithLifecycle()์ StateFlow๋ฅผ ์ปดํฌ์ฆ์ State๋ก ๋ฐ๊ฟ์ฃผ๋ ํจ์๋ค. uiState.todoList๊ฐ ๋ฐ๋ ๋๋ง๋ค ์ด ๊ฐ์ ์ฝ๊ณ ์๋ ์ปดํฌ์ ๋ธ์ด ๋ฆฌ์ปดํฌ์ง์
๋๋ค. ํ๋ฉด์ด ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ๊ฐ ์์ ๋๋ ๊ตฌ๋
์ ์ ๊น ๋ฉ์ถฐ์(์๋ช
์ฃผ๊ธฐ๋ฅผ ์ธ์ํด์) ์ธ๋ฐ์๋ ์์
์ ์ค์ฌ์ค๋ค๋ ์ ์ด collectAsState()์ ๋ค๋ฅด๋ค.
(์ฌ๊ธฐ ์ฐ์ธ collectAsStateWithLifecycle์ androidx.lifecycle:lifecycle-runtime-compose ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๊ณตํ๋ค. ์ด ๊ธ ๊ธฐ์ค ์์ ๋ฒ์ ์ 2.11.0์ด๋ค.)
์ ๋ฆฌ
_uiState(MutableStateFlow)๋ViewModel์์์๋ง ๋ฐ๊พธ๊ณ ,uiState(StateFlow)๋ ๋ฐ๊นฅ์ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ๋ ธ์ถํ๋ค- ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ฟ ๋๋ ๋ฆฌ์คํธ๋ฅผ ์ง์ ๊ฑด๋๋ฆฌ์ง ๋ง๊ณ ,
copy()๋ก ์UiState๋ฅผ ๋ง๋ค์ด์ ํต์งธ๋ก ๊ต์ฒดํ๋ค - ์ปดํฌ์ ๋ธ์์๋
collectAsStateWithLifecycle()๋ก ๊ตฌ๋ ํด์State์ฒ๋ผ ์ฝ๋๋ค
๋ค์ ๊ธ์์๋ 7๋ฒ์์ ๋ง๋ to-do ์ฑ ์ ์ฒด๋ฅผ ์ง๊ธ๊น์ง(์ํ ํธ์ด์คํ โ ViewModel โ StateFlow/UiState) ๋ฐฐ์ด ๊ตฌ์กฐ๋ก ๋ค์ ์ ๋ฆฌํด์, ์ ์ฒด์ฝ๋๋ก ํ ๋ฒ์ ๋ฌถ์ด๋ณด๊ฒ ๋ค.
