[TIL] ๐ผ24/05/01๐ผ#Sealed Class์ Flow๋ก Retrofit ์๋ต ์ฒ๋ฆฌ
Sealed Class์ Flow๋ก Retrofit ์๋ต ์ฒ๋ฆฌ
- ์นด์นด์ค ์ด๋ฏธ์ง/๋์์ ๊ฒ์ API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ฑ์์
API ์๋ต์ Sealed Class์ Flow๋ก ์ฒ๋ฆฌํ๋๋ก ๋ฆฌํฉํ ๋ง์ ์งํํ๋ค!
๐์ฐธ๊ณ ์๋ฃ
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}
fun handleApiResponse(response: ApiResponse) {
when (response) {
is ApiResponse.Success -> {
}
is ApiResponse.Error -> {
}
ApiResponse.Loading -> {
}
}
}
- API ์๋ต ์ฒ๋ฆฌ์ ์ฌ์ฉํ Sealed Class ์ ์ํ๊ธฐ (ApiResponse.kt)
- ์ ๋ค๋ฆญ ํ์
์ฌ์ฉ -> ์ฌ๋ฌ API ์๋ต ์ฒ๋ฆฌ ๊ฐ๋ฅ
sealed class ApiResponse<out T>{
object Loading: ApiResponse<Nothing>()
data class Success<T>(val data: T): ApiResponse<T>()
sealed class Fail: ApiResponse<Nothing>() {
data class Error(val code: Int, val message: String?) : Fail()
data class Exception(val e: Throwable) : Fail()
}
}
- ์นด์นด์ค ์ด๋ฏธ์ง/๋์์ ๊ฒ์ API Retrofit ํต์ ์๋ต์ผ๋ก ๋ฐ๋ DTO ํด๋์ค (KakaoSearchDTO.kt)
data class KakaoSearchDTO<T:Document>(
val meta: Meta,
val documents: List<T>?
)
data class Meta(
@SerializedName("total_count")
val totalCount: Int,
@SerializedName("pageable_count")
val pageableCount: Int,
@SerializedName("is_end")
val isEnd: Boolean
)
sealed interface Document {
data class ImageDocument(
val collection: String,
@SerializedName("thumbnail_url")
val thumbnailUrl: String,
@SerializedName("image_url")
val imageUrl: String,
val width: Int,
val height: Int,
@SerializedName("display_sitename")
val displaySitename: String,
@SerializedName("doc_url")
val docUrl: String,
val datetime: String
):Document
data class VideoDocument(
val title: String,
val url: String,
val datetime: String,
@SerializedName("play_time")
val playTime: Int,
val thumbnail: String,
val author: String
):Document
}
- Repository์์ API ์๋ต(KakaoSearchDTO)์ Sealed Class(ApiResponse)๋ก ๋ณํํ์ฌ
emit
ํ๊ธฐ (ItemRepositoryImpl.kt)
class ItemRepositoryImpl @Inject constructor(
private val kakaoSearchSource: KakaoSearchApi
) : ItemRepository {
override suspend fun getImages(query: String): Flow<ApiResponse<KakaoSearchDTO<Document.ImageDocument>>> =
handleKakaoSearchDTO {
kakaoSearchSource.getImageDTO(query)
}
override suspend fun getVideos(query: String): Flow<ApiResponse<KakaoSearchDTO<Document.VideoDocument>>> =
handleKakaoSearchDTO {
kakaoSearchSource.getVideoDTO(query)
}
private fun <T:Document> handleKakaoSearchDTO(
execute: suspend () -> KakaoSearchDTO<T>
): Flow<ApiResponse<KakaoSearchDTO<T>>> = flow {
emit(ApiResponse.Loading)
try {
emit(ApiResponse.Success(execute()))
} catch (e: HttpException) {
emit(ApiResponse.Fail.Error(e.code(), e.message()))
} catch (e: Exception) {
emit(ApiResponse.Fail.Exception(e))
}
}
}
- API ์๋ต ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉํ๋ ViewModel์์
emit
๋ ๊ฐ collect
ํ์ฌ ์ฌ์ฉํ๊ธฐ (SearchViewModel.kt)
- ์ด๋ฏธ์ง ๊ฒ์ ๊ฒฐ๊ณผ์ ๋์์ ๊ฒ์ ๊ฒฐ๊ณผ
combine
์ผ๋ก ํฉ์ณ์ ์ฌ์ฉ
private val _searchItems = MutableStateFlow<List<Item>>(emptyList())
private suspend fun fetchSearchResult() {
val query = _keyword.value ?: ""
val imageResponseFlow = itemRepository.getImages(query)
val videoResponseFlow = itemRepository.getVideos(query)
imageResponseFlow.combine(videoResponseFlow) {i, v ->
val itemList = mutableListOf<Item>()
if(i is ApiResponse.Success){
itemList.addAll(i.data.documents?.map { it.convert() } ?: emptyList())
}
if(v is ApiResponse.Success){
itemList.addAll(v.data.documents?.map { it.convert() } ?: emptyList())
}
itemList.toList()
}.collect{
_searchItems.value = it
}
}
private fun Document.ImageDocument.convert() =
Item(
itemType = ItemType.IMAGE_TYPE,
imageUrl = imageUrl,
source = displaySitename,
time = datetime.formatDate()
)
private fun Document.VideoDocument.convert() =
Item(
itemType = ItemType.VIDEO_TYPE,
imageUrl = thumbnail,
source = author,
time = datetime.formatDate()
)