2023. 9. 5. 20:42ㆍandroid
오늘의 주제는 단방향 데이터 플로우에 대해 알아보겠습니다. 얼마 전 면접을 보고 왔는데 단방향 데이터
플로우에 대해 명확하고 깔끔한 대답을 하지 못했습니다. 아쉬움이 남아 평소 단방향 데이터 플로우를
사용하여 UI 아키텍처를 구성하고 있지만 개념과 명칭 아키텍처에 대해 정확하게 짚고 넘어가야겠다고
생각하였습니다. 그럼 시작하겠습니다.
단방향 데이터 플로우란 무엇인가?
먼저 단방향 데이터 플로우를 검색하면 특히 프론트엔드 개발에서 많이 사용되는 개념으로 React이나
Android에서 사용하는 내용을 많이 볼 수 있습니다. 그럼 안드로이드 공식 홈페이지에서 단방향 데이터
플로우에 대해서는 어떻게 말하고 있을까요?? 한번 들어가 보겠습니다.
https://developer.android.com/jetpack/compose/architecture
안드로이드 공홈에서는 compose ui 설계에서의 내용을 보여주며 글을 소개하고 있습니다.
compose ui에서의 단방향 데이터 플로우란 무엇일까에 대한 설명이 쭉 이어지면 데이터의
변화에 따른 UI의 변화와 state에 대해 알려주고 이벤트와 뷰모델에서의 예를 보여주고 있습니다.
위의 상태도를 보면 State 위, UI는 아래에 배치해 있고 state는 아래로 event는 위로 가고 있습니다.
이 흐름도는 무엇을 의미할까? 여기에 대해서 알려면 먼저 state가 무엇인지를 알아야겠지요.
사실 단방향 데이터 플로우에 대해 검색하고 궁금하신다면 state가 대략 무엇인지는 다들 아실 겁니다.
그럼 State는 무엇일까?
https://developer.android.com/jetpack/compose/state?hl=ko
State가 무엇인지에 대한 설명도 안드로이드 공홈에 자세하게 나와있습니다.
그럼 여기서는 State를 무엇이라고 알려줄까요??
한 줄로 말해줍니다.
State = 앱에서 시간의 지남에 따라 변경될 수 있는 모든 값
변할 수 있는 값이라면 어떤 것이든 State라고 할 수 있다고 합니다.
하지만 위의 두 공식 홈페이지 모두 컴포즈 UI를 통해 작성된 설명들입니다.
왜 Compose UI를 통해 State와 단방향 데이터 플로우를 설명하고 있을까??
그럼 State는 Compose UI와 어떤 관계가 있지??
먼저 우리가 아는 Compose UI의 특징은 뭐가 있을까요?? 가장 큰 특징으로는 선언형 UI라는 점입니다.
선언형 UI는??
선언형 UI는 UI가 상태에 따라 무엇을 보여야 하는지를 "선언"하는 방식으로 UI를 개발하는 것입니다.
어떻게 보다는 무엇을 표현하는데 초점을 맞춘 접근법입니다.
우리가 버튼을 눌러 UI의 변경이 있다면 어떻게 보여줄 거야???
How? 보다는 버튼을 누르면 무엇을 보여줄거야???
여기에 초점을 맞춘 방식입니다. 무엇을 보여줄지 미리 만들어두고 사용자 이벤트의 변화에 따라
view를 보여주는 방식인 것입니다. 결국 상태가 어떤 상태이냐에 따라 무엇을 보여줄지 결정하는 것입니다.
영어에서 How와 what의 차이를 느끼시면 이해가 바로 됩니다.
Compose는 선언형 방식으로 미리 그려둔 view를 상태의 변화가 감지되면 새로 그립니다.
위의 코드를 보면 첫 번째 Header는 매개변수 title, subtitle 두 개가 있습니다.
이제 Header를 사용하여 View를 그린다면 여기서 title과 subtitle에 값을 넣어주면 view가
그려질 것입니다. 그럼 여기서 @Composable로 선언된 Header의 State는 무엇일까요?
바로 title과 subtitle입니다. 타이틀과 서브타이블이 변경된다면 Header는 다시 그려집니다.
그때는 변경된 타이틀과 서브타이틀을 가진 채로 보여주게 됩니다.
두 번째 Header는 News 타입의 객체인 news 매개변수가 들어오고, 변경되면 Header가
news의 상태에 따라 다시 그려지게 됩니다. 이때 다시 그려지게 되는걸 Recomposition이라고 합니다.
따라서 컴포저블 매개변수를 정의할 때는 컴포저블의 재사용 가능성 또는 유연성, 상태 매개변수가
컴포저블의 성능에 미치는 영향들을 고려하여 생성해야 합니다.
자 이제 다시 돌아와서! 정리를 해보면
State = 시간의 지남에 따라 변경될 수 있는 모든 값
선언형 UI = State의 변화에 따라 무엇을 보여줄지 미리 선언하는 UI 개발 방식
그렇다면 이제 다시! 위로 가보겠습니다.
UI는 Compose UI가 되고 여기서 ComposeUI는 state에 따라 변경되기 때문에 state는 변경될 수 있는
모든 값들이면서 Compose 함수의 상태를 변경하는 것들이 됩니다. 이러한 state가 State에 모여있고
관리되면서 아래로 내려오고 UI에서는 event가 올라갑니다. UI에서 State의 변화를 트리거하는 event는
UI에서 State 방향으로 가게 되고 이벤트가 트리거 되면 State안에서 state의 처리 후 변경된 state는
UI로 보냅니다. UI에서 변경 가능한 값들은 적을 수도 있고 많을 수도 있습니다.
이러한 state들을 관리하기 위해서 어떤 방식이 좋을지, 효과적으로 관리하기 위한
방법이 무엇이 있을까에 대한 대답이 Compose UI에서의 단방향 데이터 플로우 디자인 패턴이 아닐까 싶습니다.
어떻게 단방향 데이터 플로우를 구현할까?
ui 관련 데이터를 모아 관리하는 장소가 필요합니다. 안드로이드에서는 무엇으로 UI 관련 데이터
즉, State들을 관리할까요?? 바로 Android Jetpack 구성요소 중 하나인 ViewModel 클래스가
데이터를 관리합니다.
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko
Viewmodel의 역할은 View의 state를 관리하고 유지하는 역할을 합니다. 데이터가 필요할 때마다 불러오면
리소스가 낭비되고, 데이터의 처리가 제대로 되지 않으면 일관된 데이터를 유지하여 변경에 따른 데이터를
View에 보여주기 어렵습니다.
Viewmodel에서는 무엇으로, 어떻게 State를 관리할까???
Compose UI의 State를 어떻게 viewmodel에서 관리하고 알릴까요?? 그것은 상태 홀더를
통해 State를 관리합니다 그럼 State Holder는 무엇인가! 컴포즈 UI를 업데이트하기 위한
가장 대표적인 상태 홀더는 mutableStateOf를 사용하여 앱의 단방향 데이터 플로우를 적용
할 수 있습니다. 아래에 예를 하나 보여드리겠습니다.
@HiltViewModel
class NoteViewModel @Inject constructor(
private val saveNote: SaveNote,
private val getNote: GetNote
) : ViewModel() {
var state = mutableStateOf(NoteState())
private set
private var noteId: Int? = null
private val title get() = state.value.title
private val content get() = state.value.content
private val color get() = state.value.color
fun loadNote(id: Int) {
viewModelScope.launch {
val note = getNote(id)
noteId = id
state.value = state.value.copy(
title = note.title,
content = note.content,
color = note.color,
)
}
}
fun onTitleChanged(newTitle: String) {
state.value = state.value.copy(title = newTitle)
}
fun onContentChanged(newContent: String) {
state.value = state.value.copy(content = newContent)
}
fun onColorChanged(newColor: Int) {
state.value = state.value.copy(color = newColor)
}
fun onColorSectionVisible(colorSection: Boolean) {
state.value = state.value.copy(colorSection = !colorSection)
}
suspend fun onSaveClicked(popUpScreen: () -> Unit) {
if (title.isNotBlank()) {
saveNote().join()
popUpScreen()
} else SnackBarManager.showMessage(AppText.titleHint)
}
private fun saveNote() = viewModelScope.launch(Dispatchers.IO) {
saveNote(
Note(
id = noteId,
title = title,
content = content,
color = color,
timestamp = System.currentTimeMillis()
)
)
}
fun onBack(popUpScreen: () -> Unit) = popUpScreen()
}
위의 코드에 var state 변수는 mutableStateOf()를 사용하여 NoteState() 데이터 클래스에 대한
상태 홀더를 가집니다. 상태 홀더를 통해 Compose에서 관찰 가능하게 합니다. 관찰 가능하다는 말은
mutableStateOf로 선언된 변수를 통해 값(= state)이 변경되면 그 값을 읽는 Composable 함수에서
Recomposition이 발생합니다. 상태 홀더에는 대표적으로 몇 가지가 있습니다.
https://developer.android.com/topic/architecture/ui-layer/stateholders?hl=ko
- mutableStateOf
- LiveData
- StateFlow
- Observable
다양한 API를 통해 State Holder를 구현할 수 있습니다. 중요한 점은 ViewModel에서
State Holder를 통해 state의 변경을 확인, 관리하는 것입니다. 변경 방식은 위 코드에
각 Changed 함수를 보시면 됩니다.
fun onTitleChanged(newTitle: String) {
state.value = state.value.copy(title = newTitle)
}
state.value를 통해 state에 저장되어 있는 변경하지 않는 값들은 copy 하고 변경하는 값은 newTitle로
변경하여 state가 변경됩니다. 이후 state.title을 읽어오는 composable함수에서 state의 변경이 발생하면
변경점을 파악하고 state.title을 매개변수로 받는 composable 함수가 리컴포지션합니다.
이러한 단방향 데이터 플로우 디자인 패턴의 장점은 무엇일까요???
- 테스트 가능성
- 상태 캡슐화
- UI 일관성
장점으로는 당연히 분리도가 높아집니다. 그로 인해 테스트 가능성, 테스트하기에 용이하게 됩니다.
또한 따로 관리하기 때문에 일관되게 유지하고 오류 발생 영역이 감소하여 빠르게 파악할 수 있게 됩니다.
좀만 생각해 보면 알 수 있는 장점들입니다. 패턴의 사용은 결국 더 편리하고 같은 문제를 풀기 위해
또 만들지 않기 위함이니까요. 또 Readablilty 한 코드도 만들기 쉽습니다. 읽기 쉽다면 유지보수에도
편리하겠죠. 장점은 뭐 얘기하면 더 나오겠지만 아무튼! 이러한 장점이 있습니다.
결론
단방향 데이터 흐름은 변경 가능한 state를 ViewModel의 State Holder를 통해 관리하고 UI에서는
이벤트로 State의 변경을 트리거하며 ViewModel에서 State Holder를 관리하여 State의 변화를 UI에
알리는 디자인 패턴이다!!
+ 좀 더 확장하면 UI에서 발생한 이벤트로 인해 트리거 된 데이터 요청은 한쪽 방향으로만 흐르고
그로 인한 데이터 callback, 비동기 작업, state의 변경 등은 반대로 흘러 결과를 알려주는 단방향의
데이터 처리 흐름을 단방향 데이터 플로우라고 할 수 있습니다.
'android' 카테고리의 다른 글
드로이드 나이츠 후기!! (0) | 2023.09.12 |
---|---|
드로이드 나이츠가 벌써 코 앞이라니 (0) | 2023.09.11 |
Android Version Catalog란? (0) | 2023.07.31 |
Google I/O Extended 2023 Seoul 후기 (0) | 2023.07.31 |
Android Bitmap rotate, resize, combine Kotlin (0) | 2023.07.28 |