[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 λ“±μ˜ μ˜ˆμ™Έμ μΈ 경우λ₯Ό μ‰½κ²Œ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.