[Android] Tech,Study
μν κ΄λ¦¬μ UIState
nunukim
2024. 9. 12. 17:28
π‘μ°μνν ν¬μ½μ€ κ³Όμ μμ μ κ΄μ©ν μν κ΄λ¦¬μ UIStateμ λνμ¬ μ 리νμμ΅λλ€.
μν κ΄λ¦¬
- UIStateλ μνλ₯Ό μ μ₯νκ³ κ΄λ¦¬νλ ν¨ν΄μ νμ©νλ μ€μν κ°λ μ λλ€.
Androidμμ μ ν리μΌμ΄μ μμ UI λ° λ°μ΄ν°λ₯Ό κ΄λ¦¬νλ μ€μν κ°λ μ λλ€. νΉν MVVM ν¨ν΄μ΄λ Jetpack Compose λ±μμ μν κ΄λ¦¬λ νμμ μΈ μμμ΄λ©°, μ΄λ₯Ό ν΅ν΄ UIκ° μΌκ΄μ± μκ² λ°μ΄ν°λ₯Ό λ°μνκ³ μ¬μ©μμ μ‘μ μ λ°λΌ λμ μΌλ‘ μ λ°μ΄νΈ ν μ μμ΅λλ€.
State ManageMent
- μλλ‘μ΄λ μ ν리μΌμ΄μ
μ μ¬μ©μ μΈν°νμ΄μ€κ° μλμ κ°μ΄ λ€μν μνλ₯Ό κ°μ§ μ μμ΅λλ€.
- Loading
- Success
- Error
- Init
- UI μνλ₯Ό λνλ΄λ ν΄λμ€λ μν κ°μ²΄λ₯Ό νμ©νλ©΄ UIμ ViewModelμ΄ μνΈμμ©νμ¬ μνμ λ°λ₯Έ UIλ₯Ό κ°±μ ν μ μμ΅λλ€.
UI State ν¨ν΄
- κ° μνλ₯Ό λͺ μμ μΌλ‘ μ μνκ³ , μ΄λ₯Ό ν΅ν΄ UIκ° μ΄λ€ μνμ μλμ§ νμΈν μ μμ΅λλ€.
Sealed interface
- Sealed interfaceμ Data Objectλ§μ νμ©ν΄μ UI Stateλ₯Ό ꡬνν μ μμ΅λλ€.
sealed interface ClubListUiState {
data object Init : ClubListUiState
data object NotData : ClubListUiState
data object NotAddress : ClubListUiState
data object Error : ClubListUiState
}
- sealed interfaceλ₯Ό νμ©ν μμμ±λ₯Ό ꡬννκ³ , λΆκΈ° μ²λ¦¬λ₯Ό ν΅ν΄μ κ° μν©μ λ§λ UIλ₯Ό λμν©λλ€.
viewModel.uiState.observe(viewLifecycleOwner) { state ->
when (state) {
ClubListUiState.Init -> applyViewVisibility(binding.includeClubList.rcvClubListClub)
ClubListUiState.NotAddress -> applyViewVisibility(binding.includeClubAddress.linearLayoutClubNotAddress)
ClubListUiState.NotData -> applyViewVisibility(binding.includeClubData.linearLayoutClubNotData)
ClubListUiState.Error -> applyViewVisibility(binding.includeClubError.linearLayoutClubError)
}
}
- sealed Interfaceμ data objectλ₯Ό νμ©νλ©΄ μν©μ λ§λ λ·°λ₯Ό μ λ°μ΄νΈνλ μ₯μ μ΄ μμ§λ§, λ°μ΄ν°λ₯Ό μ λ¬νκ³ κ΄λ¦¬νκΈ° μ΄λ ΅μ΅λλ€.
- μ΄λ₯Ό ν΄κ²°νκΈ° μν΄μ Sealed Classμ data Classλ₯Ό ν¨κ» νμ©ν μ μμ΅λλ€.
Sealed Class
- sealed Classλ data Classμ ν¨κ» μ¬μ©νμ¬ μνλ₯Ό κ°μ²΄λ‘ κ΄λ¦¬ν μ μμ΅λλ€.
- Sealed Classλ‘ UI μνλ₯Ό μ μνλ©΄ μνμ λ§λ κ°μ μ μ₯ν μ μμ΅λλ€.
sealed class UIState<out T> {
object Loading : UIState<Nothing>()
data class Success<out T>(val data: T) : UIState<T>()
data class Error(val exception: Throwable) : UIState<Nothing>()
object Empty : UIState<Nothing>()
}
- κ° μνμ λ§λ κ°μ μ μ₯νκ³ , μν λ³ν μ Viewλ‘ dataλ₯Ό μ λ¬ν μ μμ΅λλ€.
ViewModel νμ©
- ViewModelμμ LiveDataλ StateFlowλ₯Ό μ¬μ©νμ¬ μμ±ν UIStateλ₯Ό κ΄λ¦¬ν μ μμ΅λλ€.
- μ΄λ₯Ό ν΅ν΄μ μ¬μ©μμ μ‘μ μ λ°λΌ λ°μ΄ν°λ₯Ό λ‘λνκ³ , UIStateλ₯Ό μ λ°μ΄νΈν μ μμ΅λλ€.
class MyViewModel : ViewModel() {
private val _uiState = MutableLiveData<UIState<List<String>>>()
val uiState: LiveData<UIState<List<String>>> get() = _uiState
fun fetchData() {
_uiState.value = UIState.Loading
viewModelScope.launch {
try {
val data = repository.getData() // λ°μ΄ν° λ‘λ
if (data.isEmpty()) {
_uiState.value = UIState.Empty
} else {
_uiState.value = UIState.Success(data)
}
} catch (e: Exception) {
_uiState.value = UIState.Error(e)
}
}
}
}
UIμμ μν λ λλ§
- UI μμ ViewModelμ΄ κ΄λ¦¬νλ UIStateλ₯Ό ꡬλ νμ¬ μνμ λ°λΌ UIλ₯Ό μ λ°μ΄νΈν μ μμ΅λλ€.
@Composable
fun MyScreen(viewModel: MyViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.observeAsState()
when (uiState) {
is UIState.Loading -> {
// λ‘λ© νλ©΄ νμ
CircularProgressIndicator()
}
is UIState.Success -> {
// λ°μ΄ν°κ° μ±κ³΅μ μΌλ‘ λ‘λλμμ λ
val data = (uiState as UIState.Success).data
Text("Data: $data")
}
is UIState.Empty -> {
// λ°μ΄ν°κ° μμ λ
Text("No data available.")
}
is UIState.Error -> {
// μλ¬ λ°μ μ
val error = (uiState as UIState.Error).exception
Text("Error: ${error.message}")
}
}
}
- λ‘λ© μ±κ³΅, μ€ν¨, μλ£ λ± λ€μν μνμ λ°λΌ UIκ° λ λλ§ λ©λλ€.
μν κ΄λ¦¬μ μ€μμ±
- UIStateλ μ±μμ λ€μν μνλ₯Ό κ΄λ¦¬νκ³ , κ·Έ μνμ λ°λΌ UIλ₯Ό λμ μΌλ‘ λ³νμν¬ μ μλλ‘ λμμ£Όλ μ€μν ν¨ν΄μ λλ€.
- Androidμμλ ViewModelκ³Ό LiveData, StateFlowλ₯Ό μ¬μ©νμ¬ μ΄λ¬ν μνλ₯Ό κ΄λ¦¬νκ³ UIκ° μ΄λ₯Ό ꡬλ νλλ‘ νμ¬ μ μ ν νλ©΄μ λ λλ§ν μ μμ΅λλ€.
μΌκ΄μ± μλ UI μ 곡
- μ±μ μν λ³νμ λ°λΌ μΌκ΄λκ² UIλ₯Ό κ°±μ ν μ μμ΅λλ€.
μ μ§λ³΄μμ±
- κ° μνμ λν μ²λ¦¬κ° λΆλ¦¬λμ΄ μμΌλ―λ‘ μ½λκ° λ μ½κΈ° μ½κ³ μ μ§λ³΄μνκΈ° μ¬μμ§λλ€.
λ²κ·Έ λ°©μ§
- μνμ λ°λΌ UIλ₯Ό λͺ μμ μΌλ‘ μ²λ¦¬ν¨μΌλ‘μ¨ μ€λ₯, λΉ List λ±μ μμΈμ μΈ κ²½μ°λ₯Ό μ½κ² μ²λ¦¬ν μ μμ΅λλ€.