⛔ 문제사항
당시 코드다. channelList는 LiveData로 관리하고 있고, channel을 가져오는 api는 async await을 통해 비동기적으로 받아오고 있었다.
class HomeViewModel(
private val channelRepository: ChannelRepository = ChannelRepositoryImpl()
) : ViewModel() {
private val idList = listOf(
"@JBKWAK", "@PaniBottle", "@im1G", "@soy_the_world", "@tripcompany93", "@jojocamping", "@CHOMAD", "@Birdmoi", "@kimhanryang97", "@YongZinCamp",
"@sookoh수코", "@CHACHABBO_VLOG", "@nanajane", "@DarongT", "@koreanjay", "@korea_travel", "@chabakchabak", "@awesomebackpakers", "@Birdmoi", "@_davidghc",
)
private val _channelList = MutableLiveData<List<ChannelListModel>>()
val channelList: LiveData<List<ChannelListModel>> = _channelList
fun fetchChannel() {
val newIdList = idList.shuffled().slice(0..5)
val currentList = _channelList.value?.toMutableList() ?: mutableListOf()
newIdList.forEach { it ->
viewModelScope.launch {
runCatching {
val fetchResult = async { return@async channelRepository.getChannel(it) }
val result = fetchResult.await()
currentList.add(result.toChannelListData())
_channelList.value = currentList
}.onFailure {
Log.e("💡HomeViewModel fetchChannel", "fetchChannel() onFailure: ${it.message}")
}
}
}
}
fun clearList() {
_channelList.value = listOf()
}
}
✅ 해결방안
이러한 버그가 발생한 원인으로는 바로, LiveData가 Adapter와 sync가 맞지 않아서였다.
fetchChannel 함수 내부의 forEach문을 돌면서 channelRepository.getChannel(it)이 호출될 때마다 LiveData가 바뀌고, 이 LiveData가 바뀔 때마다 Adapter를 다시 그리면서 타이밍이 어그러져 발생하는 문제였다.
1. Job을 이용한 해결방안
Job을 별도로 선언하여 forEach문이 돌 때마다 job을 추가해주고, 반복문 실행이 완료되면 jobs.joinAll() 해주어 LiveData에 안전하게 반영해주는 방법이 있다.
class HomeViewModel(
private val channelRepository: ChannelRepository = ChannelRepositoryImpl()
) : ViewModel() {
private val idList = listOf(
"@JBKWAK", "@PaniBottle", "@im1G", "@soy_the_world", "@tripcompany93", "@jojocamping", "@CHOMAD", "@Birdmoi", "@kimhanryang97", "@YongZinCamp",
"@sookoh수코", "@CHACHABBO_VLOG", "@nanajane", "@DarongT", "@koreanjay", "@korea_travel", "@chabakchabak", "@awesomebackpakers", "@Birdmoi", "@_davidghc",
)
private val _channelList = MutableLiveData<List<ChannelListModel>>()
val channelList: LiveData<List<ChannelListModel>> = _channelList
val jobs = mutableListOf<Job>()
fun fetchChannel() {
val newIdList = idList.shuffled().slice(0..5)
val currentList = _channelList.value?.toMutableList() ?: mutableListOf()
newIdList.forEach { it ->
val job = viewModelScope.launch {
runCatching {
val fetchResult = async { return@async channelRepository.getChannel(it) }
val result = fetchResult.await()
currentList.add(result.toChannelListData())
_channelList.value = currentList
}.onFailure {
Log.e("💡HomeViewModel fetchChannel", "fetchChannel() onFailure: ${it.message}")
}
}
jobs.add(job)
}
jobs.add(job)
viewModelScope.launch {
jobs.joinAll()
_channelList.value = currentList
}
}
fun clearList() {
_channelList.value = listOf()
}
}
2. UseCase를 이용한 해결방안
channelRepository.getChannel(it) 함수 호출을 UseCase로 분리하여 서버에서 가져오는 데이터를 LiveData와 아예 분리시키는 해결방안이 있다.
class HomeViewModel(
private val channelRepository: ChannelRepository = ChannelRepositoryImpl(),
private val channelUseCase: ChannelUseCase = ChannelUseCase(ChannelRepositoryImpl())
) : ViewModel() {
private val idList = listOf(
"@JBKWAK", "@PaniBottle", "@im1G", "@soy_the_world", "@tripcompany93", "@jojocamping", "@CHOMAD", "@Birdmoi", "@kimhanryang97", "@YongZinCamp",
"@sookoh수코", "@CHACHABBO_VLOG", "@nanajane", "@DarongT", "@koreanjay", "@korea_travel", "@chabakchabak", "@awesomebackpakers", "@Birdmoi", "@_davidghc",
)
private val _channelList = MutableLiveData<List<ChannelListModel>>()
val channelList: LiveData<List<ChannelListModel>> = _channelList
val jobs = mutableListOf<Job>()
fun fetchChannel() {
val newIdList = idList.shuffled().slice(0..5)
viewModelScope.launch {
val list = channelUseCase.getChannelList(newIdList)
_channelList.value = list
}
}
fun clearList() {
_channelList.value = listOf()
}
}
class ChannelUseCase(
private val channelRepository: ChannelRepository,
) {
suspend fun getChannelList(idList: List<String>): List<ChannelListModel> {
var list: MutableList<ChannelListModel> = mutableListOf()
for (i in idList.indices) {
list.add(channelRepository.getChannel(idList[i]).toChannelListData())
}
return list
}
}
이렇게 UseCase로 분리하면 화면에 직접 영향을 주는 LiveData가 실제 데이터 List<ChannleListModel>로 분리되면서 보다 안전하게 값을 가져올 수 있으며, 비즈니스 로직을 ViewModel로부터 분리하여 효율적으로 코드 관리가 가능하다는 점에서 좀 더 유용하다.
그럼 이렇게 내가 원하는 대로 총 6개의 채널이 어떤 상황에서도 제대로 보여진다!
❗출처
튜터님의 아름다운 코칭
'Android > 문제해결' 카테고리의 다른 글
문제해결 : Supplied auth credential is incorrect, malformed or has expired (feat. ActivityResultLauncher) (3) | 2024.09.07 |
---|---|
문제해결 : Error: com.google.android.gms.common.api.ApiException: 10 (4) | 2024.08.31 |
문제해결 : Inconsistency detected. Invalid view holder adapter positionHolder (0) | 2024.08.09 |
문제해결 : Unresolved reference: BuildConfig (0) | 2024.08.01 |
문제해결 : Val cannot be reassigned (0) | 2024.07.29 |