UI의 역할은 화면에 애플리케이션 데이터를 표시하고 사용자 상호작용의 기본 지점으로도 기능하는 것입니다. 사용자 상호작용(예: 버튼 누르기) 또는 외부 입력(예: 네트워크 응답)으로 인해 데이터가 변할 때마다 변경사항을 반영하도록 UI가 업데이트되어야 합니다. 사실상 UI는 데이터 레이어에서 가져온 애플리케이션 상태를 시각적으로 나타냅니다.
하지만 일반적으로 데이터 레이어에서 가져오는 애플리케이션 데이터는 표시해야 하는 정보와 다른 형식입니다. 예를 들어 UI용으로 데이터의 일부만 필요하거나 사용자에게 관련성 있는 정보를 표시하기 위해 서로 다른 두 데이터 소스를 병합해야 할 수도 있습니다. 적용하는 로직과 관계없이 완전히 렌더링하는 데 필요한 모든 정보를 UI에 전달해야 합니다. UI 레이어는 애플리케이션 데이터 변경사항을 UI가 표시할 수 있는 형식으로 변환한 후에 표시하는 파이프라인입니다.
UI 상태 노출
UI 상태를 정의하고 이 상태의 생성을 관리할 방법을 결정한 후에는 생성된 상태를 UI에 표시하는 단계를 진행합니다. UDF를 사용하여 상태 생성을 관리하므로 생성된 상태를 스트림으로 간주할 수 있습니다. 즉, 시간 경과에 따라 여러 버전의 상태가 생성됩니다. 따라서 LiveData 또는 StateFlow와 같이 관찰 가능한 데이터 홀더에 UI 상태를 노출해야 합니다. 이유는 ViewModel에서 데이터를 직접 가져오지 않고도 UI가 상태 변경사항에 반응할 수 있도록 하기 위해서입니다. 이러한 유형은 항상 최신 버전의 UI 상태를 캐시한다는 이점도 있습니다. 이는 구성 변경 후 빠른 상태 복원에 유용합니다.
class NewsViewModel(...) : ViewModel() {
val uiState: StateFlow<NewsUiState> = …
}
UiState 스트림을 만드는 일반적인 방법은 ViewModel에서 지원되는 변경 가능한
스트림을 변경 불가능한 스트림으로 노출하는 것입니다. 예를 들어
MutableStateFlow<UiState>를 StateFlow<UiState>로 노출합니다.
class NewsViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
...
}
그런 다음 ViewModel은 상태를 내부적으로 변경하는 메서드를 노출하여 UI에 사용되도록 업데이트를 게시합니다. 예를 들어 비동기 작업을 실행해야 하는 경우
viewModelScope를 사용하여 코루틴을 실행하고
코루틴이 완료되면 변경 가능한 상태를 업데이트할 수 있습니다.
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 상태 사용
UI에서 관찰 가능한 데이터 홀더를 사용할 때는 UI의 수명 주기를 고려해야 합니다. 수명 주기를 고려해야 하는 이유는 사용자에게 뷰가 표시되지 않을 때 UI가 UI 상태를 관찰해서는 안 되기 때문입니다. 이 주제에 관한 자세한 내용은 이 블로그
게시물을 참고하세요.
LiveData를 사용하면 LifecycleOwner가 수명 주기 문제를 암시적으로 처리합니다. 흐름을 사용할 때는 적절한 코루틴 범위와 repeatOnLifecycle API로 처리하는 것이 가장 좋습니다.
class NewsActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// Update UI elements
}
}
}
}
}
진행 중인 작업 표시
UiState 클래스의 로드 상태를 나타내는 간단한 방법은 불리언 필드를 사용하는 것입니다.
data class NewsUiState(
val isFetchingArticles: Boolean = false,
...
)
이 플래그의 값은 UI에 진행률 표시줄이 존재하는지를 나타냅니다.
class NewsActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Bind the visibility of the progressBar to the state
// of isFetchingArticles.
viewModel.uiState
.map { it.isFetchingArticles }
.distinctUntilChanged()
.collect { progressBar.isVisible = it }
}
}
}
}
애니메이션
부드럽고 원활한 최상위 탐색 전환을 제공하기 위해 다음 화면의 데이터가 로드될 때까지 기다린 후에 애니메이션을 시작하는 것이 좋습니다.
Android 뷰 프레임워크는 postponeEnterTransition() 및 startPostponedEnterTransition() API를 사용하여 프래그먼트 대상 간의 전환을 지연하는 후크를 제공합니다. 이러한 API는 다음 화면의 UI 요소 (일반적으로 네트워크에서 가져온 이미지)가 표시될 준비가 되면 UI가 다음 화면으로의 전환을 애니메이션 처리할 수 있도록 합니다.
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- UI 상태 생성
- 상태 홀더 및 UI 상태{:#mad-arch}
- 앱 아키텍처 가이드