

UI elements : 화면에 데이터를 렌더링 하는 UI 요소 → 뷰 !State holders : 데이터를 보유하고 이를 UI에 노출하며 로직 처리하는 상태 홀더 → 뷰 모델 !UI : 사용하는 API(뷰 or Jetpack Compose)와 관계없이 데이터를 표시하는 활동 및 프래그먼트와 같은 UI 요소

UI elements : 각종 뷰 요소들 + 속성 등
UI State : 뷰 요소에 들어갈 데이터 + 상태
ViewModel에서 UI 상태 관리하고, View 에서는 ViewModel 옵저버 패턴을 이용해서 적절한 화면을 사용자에게 보여줌data class NewsUiState(
val isSignedIn: Boolean = false,
val isPremium: Boolean = false,
val newsItems: List<NewsItemUiState> = listOf(),
val userMessages: List<Message> = listOf()
)
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
...
)
UI 의 역할과 복잡성

State Holders : UI 상태 생성, 생성 작업에 필요한 로직 포함하는 클래스 → ViewModel

예시 UI )

ViewModel 이 Data Layer 에서 현재의 앱 데이터 가져옴 (여기서는 이미지 / 타이틀 / 작성자 / 작성 시간 / 북마크 체크 여부)View 에서 현재의 UI State 를 옵저빙해서 UI 요소에 적용View 에서 북마크를 해제했을 때(UI 이벤트 발생) ViewModel 에게 알림 (여기서 알린다는 뜻은 ViewModel 이 View 를 참조하고 있다는 것이 아닌, View 에서 ViewModel 의 메서드를 호출함. 즉, MVVM 패턴에서 “ViewModel 은 View 를 몰라야 한다” 라는 것에 위배되지 않음)ViewModel 은 Data Layer 에게 데이터 수정을 요청함Data Layer 에서는 data를 업데이트하고, 앱 데이터를 수정함ViewModel 에서는 새로운 데이터를 받아옴데이터 일관성 : UI 용 정보 소스가 하나 → SSOT테스트 가능성 : 상태가 분리되어 있어 UI 와 별개로 테스트 가능유지 관리성 : 상태 변화가 일관된 방식으로 처리되어 코드를 쉽게 수정, 이해, 확장할 수 있음LiveDataStateFlow→ 관찰 가능한 데이터 홀더에 UI 상태 노출
→ 그 이유로는 ViewModel 에서 데이터를 직접 가져오지 않고도 UI가 상태 변경사항에 반응할 수 있도록 하기 위함 (+ 항상 최신 버전의 UI 상태 캐싱한다는 이점)
Backing property BackingProperty : 특정 프로퍼티의 값을 저장하고 관리하기 위해 내부적으로만 사용되는 프라이빗 프로퍼티class NewsViewModel(...) : ViewModel() {
// 내부에서 사용되는 mutable한 프로퍼티
private val _uiState = MutableStateFlow(NewsUiState())
// 외부에서 사용되는 immutable한 프로퍼티
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
private val _name = MutableLiveData()
val name: LiveData<String> = _name
...
}
ViewModel 은 상태를 내부적으로 변경하는 메서드 노출해서 UI에 사용되도록 함. 비동기 viewModelScope 사용해 코루틴 실행 및 완료 시 mutable한 상태 업데이트
class NewsViewModel(
private val repository: NewsRepository,
...
) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
private var fetchJob: Job? = null
fun fetchArticles(category: String) {
fetchJob?.cancel()
fetchJob = viewModelScope.launch {
try {
val newsItems = repository.newsItemsForCategory(category)
_uiState.update {
it.copy(newsItems = newsItems)
}
} catch (ioe: IOException) {
// Handle the error and notify the UI when appropriate.
_uiState.update {
val messages = getMessagesFromThrowable(ioe)
it.copy(userMessages = messages)
}
}
}
}
}
UI에서 관찰 가능한 데이터 홀더 사용 시에는 생명 주기를 고려해야 함!
LiveData 는 생명주기를 인식해서 LifecycleOwner 에게 업데이트 전송함Flow 는 비동기 데이터 스트림 처리 시 사용됨 → 생명주기 직접 관리해야 함. lifecycleScope or repeatOnLifecycle 함께 사용하는 것이 좋음collect() 메서드는 flow가 방출하는 각 값을 받아 처리하는 터미널 연산자collect() 메서드는 코루틴 컨텍스트 내에서 비동기적으로 실행되며, 백그라운드에서 flow 를 수행될 수 있음 의미함더 자세한 내용은 아래 참고 자료 중 UI 레이어를 읽어보시면