ํญ๋ชฉ | StateFlow | LiveData |
---|---|---|
๋ฐ์ดํฐ ๋ฐฉ์ | StateFlow / SharedFlow (์ฝ๋ฃจํด ๊ธฐ๋ฐ) | LiveData (Lifecycle-aware) |
์คํ ์์ | ํญ์ ์ต์ ๊ฐ์ ์ ์งํ๊ณ , collectํ๋ ์๊ฐ ๋ฐ๋ก ์ ๋ฌ/ ViewModel ์์์ ๊ณ์ ๋์๊ฐ๊ณ , collect๋ง ์์ผ๋ฉด ์ธ์ ๋ ๋ฐ์ | Observer๊ฐ ํ์ฑ ์ํ์ผ ๋๋ง ์๋ ๋ฐ์/ LiveData๋ onStart()~onStop() ์ฌ์ด๋ง ๋ฐ์ |
์ ์ธ ๋ฐฉ์ | MutableStateFlow, MutableSharedFlow ์ฌ์ฉ | MutableLiveData ์ฌ์ฉ |
๊ด์ฐฐ ๋ฐฉ๋ฒ | collect ์ฌ์ฉ (์: lifecycleScope.launch { flow.collect {} }) | observe ์ฌ์ฉ (์: liveData.observe(...)) |
- ๋น๋๊ธฐ ์คํธ๋ฆผ ์ฒ๋ฆฌ์ ์ ํฉ | - Android ์๋ช ์ฃผ๊ธฐ ์๋ ๊ด๋ฆฌ | |
ํน์ง | - Kotlin Coroutines ์นํ์ | - UI ์ปจํธ๋กค๋ฌ์ ์ต์ ํ |
- ์ค๋ณต ์ด๋ฒคํธ ๋ฐฉ์ง ์ ์ฐํจ (SharedFlow) | ||
ViewModel-UI ์ฐ๊ฒฐ ๋ฐฉ์ | Flow๋ก ๋น๋๊ธฐ ์ฐ์์ฑ / ์ํ ๋ณ๊ฒฝ ๋์ | UI ์ค์ฌ์ ๋จ์ผ ์๋ต ์ฒ๋ฆฌ์ ์ ํฉ |
๋ฐ์ดํฐ ๋์ ์ฒ๋ฆฌ | ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ ๋์ ํด์ ์ ์ฅ / .update๋ก ๊ฐํธ | ์ด์ ๊ฐ์ ๋ฎ์ด์ (๋์ X) / ๋์ ํ๋ ค๋ฉด ๋ณ๋ ๋ก์ง ํ์ (ex. .value = value + ์ ํญ๋ชฉ) |
๐ก ํต์ฌ
class ContentsViewModel : ViewModel() {
private val repository = ContentsRepository()
private val _contentsGenreListStateFlow = MutableStateFlow<List<ContentsGenreData>>(emptyList())
val contentsGenreListStateFlow: StateFlow<List<ContentsGenreData>> = _contentsGenreListStateFlow
private val _contentsGenreErrorFlow = MutableSharedFlow<String>()
val contentsGenreErrorFlow: SharedFlow<String> = _contentsGenreErrorFlow
private val _contentsListStateFlow = MutableStateFlow<Map<String, List<String>>>(emptyMap())
val contentsListStateFlow: StateFlow<Map<String, List<String>>> = _contentsListStateFlow
private val _contentsListErrorFlow = MutableSharedFlow<String>()
val contentsListErrorFlow: SharedFlow<String> = _contentsListErrorFlow
fun getGenreList() {
viewModelScope.launch {
try {
val response = repository.getGenreList()
if (response.isSuccess) {
_contentsGenreListStateFlow.value = response.getOrThrow().data.genre
} else {
_contentsGenreErrorFlow.emit("์ฅ๋ฅด ๋ชฉ๋ก ๋ถ๋ฌ์ค๊ธฐ ์คํจ")
}
} catch (e: Exception) {
_contentsGenreErrorFlow.emit("์๋ฌ: ${e.message}")
}
}
}
fun getContentsList(genreCode: String) {
viewModelScope.launch {
try {
val response = repository.getContentsList(genreCode)
if (response.isSuccess) {
val contentsMap = response.getOrThrow().data.contents.associate {
it.contentsName to listOf(it.contentsCode, it.developmentalElementName)
}
_contentsListStateFlow.update { it + contentsMap } // ๋์ ์ ์ฅ
} else {
_contentsListErrorFlow.emit("์ฝํ
์ธ ๋ชฉ๋ก ์คํจ (์ฅ๋ฅด์ฝ๋: $genreCode)")
}
} catch (e: Exception) {
_contentsListErrorFlow.emit("์๋ฌ: ${e.message}")
}
}
}
}
### ๋ทฐ์์
contentsViewModel = ViewModelProvider(this)[ContentsViewModel::class.java]
Common.getGenreCode(requireContext(),binding.tvTitle.text.toString())
?.let {
Log.d("SmartFragment","ํ์ธ : $it")
Common.GENRE_CODE = it
contentsViewModel.getContentsList(it)
}
private fun observeContents() {
// ์ฝํ
์ธ ๋ฆฌ์คํธ ์์
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
contentsViewModel.contentsListStateFlow.collect { contentsMap ->
Common.CONTENTS_LIST_MAP.clear()
Common.CONTENTS_LIST_MAP.putAll(contentsMap)
// UI ๋ฐ์ํ๊ฑฐ๋ ๋ก๊ทธ ์ฐ๊ธฐ ๋ฑ ํ์ ์์
์ํ
Log.d("SmartFragment","SmartFragment => ${contentsMap}")
if (contentsMap.isNotEmpty()) {
withContext(Dispatchers.Main) {
attachMainStagePlayFragment(contentsMap)
}
}
}
}
}
lifecycleScope.launch {
contentsViewModel.contentsListErrorFlow.collect {
Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show()
}
}
}
๐ ์ฑ์ด ์คํ ์ค์ธ ๋ด๋ด ๊ณ์ ์ํ๋ฅผ ์ ์งํ๊ณ , ๋๊ฐ ์๋ก ๋ค์ด์๋ ๊ฐ์ฅ ์ต์ ์ํ๋ฅผ ๋ณด์ฌ์ค์ผ ํ ๋
cf) SharedFlow
- ๋๊ตฐ๊ฐ emit("์๋ฌ ๋ฌ์ด์!")๋ผ๊ณ ๋งํ๋ฉด, ๊ทธ ์๊ฐ ๋ฃ๊ณ ์๋ ์ฌ๋ ๋ค์
- StateFlow๋ ๋น์ทํ์ง๋ง ์ด์ ์ ๋งํ ๊ฑด ๊ธฐ์ต ์ํจ (๊ฐ ์ ์ฅ ์ ํจ)
- ์๋ฌ ๋ฉ์์ง, ํ ์คํธ ์๋ฆผ ๋ฑ ํ ๋ฒ๋ง ๋ณด์ฌ์ค ์ด๋ฒคํธ์ ์ ํฉ
val toastMessage = MutableSharedFlow<String>( replay = 0, // ๊ณผ๊ฑฐ ๋ฉ์์ง ์ ์ฅ ์ ํจ extraBufferCapacity = 1 // ๋ฒํผ 1๊ฐ ํ์ฉ ) // ํ๋ฉด์์ collect lifecycleScope.launch { viewModel.toastMessage.collect { Toast.makeText(context, it, LENGTH_SHORT).show() } }
๐ก ํต์ฌ
class CompareRecordViewModel : ViewModel() {
private val repository = CompareRecordRepository()
private val _compareRecordLiveData = MutableLiveData<CompareRecordResponse>()
val compareRecordLiveData: LiveData<CompareRecordResponse> get() = _compareRecordLiveData
private val _compareRecordErrorLiveData = MutableLiveData<String>() // ์คํจ ๋ฉ์์ง ์ ์ฅ์ฉ
val compareRecordErrorLiveData: LiveData<String> get() = _compareRecordErrorLiveData
fun getCompareRecord(genreCode:String,childId:String) {
viewModelScope.launch {
try {
val response = repository.getCompareRecord(genreCode,childId)
if (response.isSuccess) {
_compareRecordLiveData.postValue(response.getOrThrow())
} else {
val errorMessage = response.exceptionOrNull()?.message ?: "์ ์ ์๋ ์ค๋ฅ ๋ฐ์"
_compareRecordErrorLiveData.postValue(errorMessage)
}
} catch (e: Exception) {
val errorMessage = "๋น๊ต ๊ธฐ๋ก ํ์ฑ ์ค ์ค๋ฅ ๋ฐ์: ${e.message}"
Log.d("TAG", errorMessage)
_compareRecordErrorLiveData.postValue(errorMessage)
}
}
}
}
####ํ๋ฉด
private lateinit var compareViewModel: CompareRecordViewModel
compareViewModel = ViewModelProvider(this)[CompareRecordViewModel::class.java]
private fun registerViewModelObserve() {
compareViewModel.compareRecordLiveData.observe(viewLifecycleOwner) { response ->
if (response.status) {
//๋ทฐ ํ์ด์ ์ ์บ์ฑ ์ญ์ ํ !! ์ฌ๊ธฐ๊ฐ ์์ฃผ ํ์
binding.radarChart.options.series = arrayListOf()
binding.radarChart.update(binding.radarChart.options, true, true)
val newData = response.data
updateRadarDataToUi(newData)
}
}
compareViewModel.compareRecordErrorLiveData.observe(viewLifecycleOwner) { error ->
Toast.makeText(requireContext(), error, Toast.LENGTH_SHORT).show()
Log.e("ERROR", "RadarChartFragment compareRecordError : $error")
}
}
private fun updateRadarData() {
compareViewModel.getCompareRecord(
Common.GENRE_CODE,
Common.getSharedPreferencesAiFitParent(requireContext(), "selectedChildId").toString()
)
}
๐ ํ๋ฉด์ด ๊บผ์ก๋ค๊ฐ ๋ค์ ์ผ์ง ๋ ์๋์ผ๋ก ๋ค์ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค์ผ ํ ๋, ๋ฐ์ดํฐ๊ฐ ๊ทธ๋ ๊ฒ ์์ฃผ ๋ฐ๋์ง ์์ ๋
ํญ๋ชฉ | StateFlow | LiveData |
---|---|---|
์๋ฌ ์ ๋ฌ ๋ฐฉ์ | MutableSharedFlow<'String> ์ฌ์ฉ โ ์ฌ๋ฌ ๋ฒ ๋ฐํ ๊ฐ๋ฅ, ์์ง์ ํ์ | MutableLiveData<'Sting> ์ฌ์ฉ โ UI์์ ๋จ์ ๊ด์ฐฐ |
์ฒ๋ฆฌ ๋ฐฉ์ | .emit(...) | .postValue(...) |
์ํฉ | ๊ถ์ฅ ๋ฐฉ์ |
---|---|
UI ์๋ช ์ฃผ๊ธฐ ๋ฐ๋ผ ์์ ํ๊ฒ ๋ฐ์ดํฐ ์ฒ๋ฆฌ | LiveData (์: ๋น๊ต ๊ธฐ๋ก) |
๋น๋๊ธฐ/์คํธ๋ฆผ/์ค๋ณต ์ด๋ฒคํธ ์ฒ๋ฆฌ ํ์ | StateFlow / SharedFlow (์: ์ฝํ ์ธ ๋ชฉ๋ก, ์ฅ๋ฅด, ์๋ฌ ์ฒ๋ฆฌ ๋ฑ) |
์ฌ๋ฌ ๊ฐ ๋์ ๋ฐ ์ค์๊ฐ ์ ๋ฐ์ดํธ ํ์ | StateFlow + .update { } ์ฌ์ฉ |
๋จ์ผ ๊ฐ ์๋ต ์ฒ๋ฆฌ์ ์ถฉ๋ถํ ๊ฒฝ์ฐ | LiveData๋ก ๊ฐ๋จ ์ฒ๋ฆฌ |
๊ตฌ๋ถ | StateFlow | LiveData |
---|---|---|
๋ชฉ์ | ์ํ ์คํธ๋ฆผ, ์ฌ๋ฌ ์ฅ๋ฅด/์ฝํ ์ธ ๋์ | ๋จ๊ฑด ๋น๊ต ๊ธฐ๋ก ์๋ต |
์ฝ๋ฃจํด | ์ฝ๋ฃจํด ๊ธฐ๋ฐ, StateFlow/SharedFlow | LiveData ๊ธฐ๋ฐ |
์์ ๋ฐฉ์ | collect {} | observe(...) |
์๋ฌ ์ฒ๋ฆฌ | SharedFlow.emit(...) | LiveData.postValue(...) |
๋ฐ์ดํฐ ๊ตฌ์กฐ | Map, List ๋ฑ์ ๋์ ์ฒ๋ฆฌ | ๋จ์ผ ๊ฐ์ฒด ์ฒ๋ฆฌ |