Room의 LiveData 응답을 Flow로 변경하는 과정을 정리해보고자 한다. 코드를 리팩토링하며 어느정도 감은 잡히는 것 같은데... Flow의 개념에 대해 다시 공부하는 시간을 가져야 할 것 같다 😵💫
@Dao
interface VideoDAO {
@Query("SELECT * FROM videos")
fun getVideos(): LiveData<List<VideoBasicModel>> // 기존 코드
import kotlinx.coroutines.flow.Flow
@Dao
interface VideoDAO {
@Query("SELECT * FROM videos")
fun getVideos(): Flow<List<VideoBasicModel>> // 수정 된 코드
interface YoutubeRepository {
suspend fun getSearchingVideos(
part: String = PART_SNIPPET,
searchText: String,
maxResults: Int = Constants.API_MAX_RESULT,
regionCode: String = Constants.API_REGION,
apiKey: String = Constants.API_KEY
): SearchModel
fun getVideos(): Flow<List<VideoBasicModel>>
class YoutubeRepositoryImpl(
private val db: VideoSearchDatabase
) : YoutubeRepository {
override suspend fun getSearchingVideos(
part: String,
searchText: String,
maxResults: Int,
regionCode: String,
apiKey: String
): SearchModel {
return RetrofitInstance.api.getSearchingVideos(searchText = searchText)
}
override fun getVideos(): Flow<List<VideoBasicModel>> =
db.videoDao().getVideos()
}
class SharedViewModel(
private val youtubeRepository: YoutubeRepository
) : ViewModel() {
val searchResults: Flow<List<VideoBasicModel>> get() = youtubeRepository.getVideos()
...
}
observe를 사용하던 LiveData와는 달리 Flow는 Coroutine 안에서 Collect나 CollectLatest를 써서 구독해야 한다.
private fun initViewModel() {
// sharedViewModel.searchResults.observe(viewLifecycleOwner) {
// homeListAdapter.submitList(it)
// }
lifecycleScope.launch {
sharedViewModel.searchResults.collectLatest {
homeListAdapter.submitList(it)
}
}
}
StateFlow로 변환하여 Flow 동작을 Fragment의 LifeCycle과 동기화 시켜준다.
class SharedViewModel(
private val youtubeRepository: YoutubeRepository
) : ViewModel() {
/*
* stateIn을 사용하여 Flow 타입을 StateFlow로 변경해준다.
* scope는 viewModelScope
* 구독을 하는 시점은 WhileSubscribed(5000)
* list는 초기값을 넣어주면 된다.
*/
val searchResults: StateFlow<List<VideoBasicModel>> get() = youtubeRepository.getVideos()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), listOf())
...
}
private fun initViewModel() {
// lifecycleScope.launch {
// sharedViewModel.searchResults.collectLatest {
// homeListAdapter.submitList(it)
// }
// }
// SateFlow의 구독이 되면서 Fragment의 LifeCycle과 연동이 된다.
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
sharedViewModel.searchResults.collectLatest {
homeListAdapter.submitList(it)
}
}
}
}
fun <T> HomeFragment.collectLatestStateFlow(flow: Flow<T>, collect: suspend (T) -> Unit) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
flow.collectLatest(collect)
}
}
}
private fun initViewModel() {
// viewLifecycleOwner.lifecycleScope.launch {
// viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// sharedViewModel.searchResults.collectLatest {
// homeListAdapter.submitList(it)
// }
// }
// }
collectLatestStateFlow(sharedViewModel.searchResults) {
homeListAdapter.submitList(it)
}
}