안녕하세요.
이번 포스팅에서는 Decompose와 MviKotlin에서 데이터를 파싱하기 위해 작업을 해보겠습니다.
전체적인 구조는 다음과 같습니다.
Clean Architecutre를 적용하여 Data, Domain 두 개의 레이어로 나누었습니다.
먼저 Domain Layer를 보겠습니다.
interface TmdbRepository {
suspend fun getMovieList(page: Int): Result<Movies>
}
DIP 조건을 충족시키기 위한 Repository의 Interface입니다.
class GetMovieListUseCase(
private val tmdbRepository: TmdbRepository
) {
suspend operator fun invoke(page: Int) = tmdbRepository.getMovieList(page)
}
다음으로 유저의 일련의 행동을 정의해주는 UseCase입니다.
간단히 데이터만 파싱하는 UseCase라 Repository의 함수를 그대로 사용해줍니다.
다음으로 Data Layer를 보겠습니다.
sealed class Result<T> {
class Success<T>(val data: T) : Result<T>()
class Error<T>(val msg: String?) : Result<T>()
}
@Serializable
data class Movies(
val page : Long,
@SerialName("total_pages")
val totalPage : Long,
@SerialName("total_results")
val totalResults : Long,
val results : List<Movie>
)
@Serializable
data class Movie(
val id : Int,
val overview : String,
val popularity : Double,
@SerialName("vote_count")
val voteCount : Int,
@SerialName("vote_average")
val voteAverage : Double,
@SerialName("original_language")
val OriginalLanguage : String,
@SerialName("backdrop_path")
val backdropPath : String?,
@SerialName("poster_path")
val posterPath : String?,
@SerialName("media_type")
val mediaType : String,
@SerialName("genre_ids")
val genres: List<Int>
)
네트워킹 결과를 처리하기 위한 Result 클래스와 Remote API로부터 받을 데이터를 정의한 Movies, Movie 입니다.
Ktor Client에서 JSON Seralizer를 사용하기 위해 Kotlin Serialization을 사용했습니다.
class NetworkTmdbDataSource(
private val httpClient: HttpClient
) {
suspend fun getMovieList(page: Int): Result<Movies> {
return try {
httpClient
.get("3/trending/all/day") {
parameter("page", page)
parameter("api_key", "...")
}
.body<Movies>()
.toResult()
} catch (e: Exception) {
Result.Error(e.message)
}
}
}
fun <T> T.toResult() = Result.Success(this)
Ktor Client를 통해 Movie List를 파싱하는 함수입니다.
저는 베타버전인 2.0.0을 사용하여서 1.6.7 버전이랑은 코드가 상이합니다. 참고해주시길 바라겠습니다.
class TmdbRepositoryImpl(
private val tmdbDataSource: NetworkTmdbDataSource
) : TmdbRepository {
override suspend fun getMovieList(page: Int): Result<Movies> = tmdbDataSource.getMovieList(page)
}
Repository의 구현체입니다. 마찬가지로 MovieList만 얻어내고 캐싱이나 다른 작업은 안 해주기 때문에 간단히 Remote Data Source의 함수만 호출해줍니다.
val networkModule = module {
single { HttpClient {
defaultRequest {
url {
protocol = URLProtocol.HTTPS
host = "api.themoviedb.org"
}
}
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
} }
single {
NetworkTmdbDataSource(get())
}
}
val repositoryModule = module {
single<TmdbRepository> { TmdbRepositoryImpl(get()) }
}
val interactorModule = module {
single { GetMovieListUseCase(get()) }
}
val networkModule = module {
single { HttpClient {
defaultRequest {
url {
protocol = URLProtocol.HTTPS
host = "api.themoviedb.org"
}
}
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
} }
singleOf(::NetworkTmdbDataSource)
}
val repositoryModule = module {
singleOf(::TmdbRepositoryImpl) { bind<TmdbRepository>() }
}
val interactorModule = module {
singleOf(::GetMovieListUseCase)
}
Koin으로 module을 설정해줍니다.
마찬가지로, Ktor Client의 2.0.0 베타 버전을 사용하여 JsonSerializer 지정해주는 코드는 1.6.7 버전과 상이합니다.
다음 장에 이어서 본격적으로 Decompose와 MviKotlin을 적용하여 비즈니스 로직을 끝내고 뷰를 그려보도록 하겠습니다.