[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()
        )