From decb3a179aa7210e1b515a9074c939dbf12c5699 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:34:14 +0900 Subject: [PATCH 01/23] =?UTF-8?q?[feat]=20:=20FlowCallAdapter=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/calladapter/flow/FlowCallAdapter.kt | 76 +++++++++++++++++++ .../flow/FlowCallAdapterFactory.kt | 44 +++++++++++ 2 files changed, 120 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapter.kt create mode 100644 app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapterFactory.kt diff --git a/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapter.kt new file mode 100644 index 00000000..ccf709e0 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapter.kt @@ -0,0 +1,76 @@ +package com.runnect.runnect.data.calladapter.flow + +import com.google.gson.Gson +import com.runnect.runnect.data.ApiResult +import com.runnect.runnect.data.dto.response.base.ErrorResponse +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.suspendCancellableCoroutine +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Callback +import retrofit2.Response +import java.lang.reflect.Type +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +class FlowApiResultCallAdapter( + private val responseType: Type +) : CallAdapter>> { + + private val gson = Gson() + + override fun responseType() = responseType + + // Retrofit의 Call을 Flow<>로 변환 + override fun adapt(call: Call): Flow> = flow { + val apiResult = suspendCancellableCoroutine { continuation -> + call.enqueue(object : Callback { + override fun onFailure(call: Call, t: Throwable) { + // 예외 발생시키고 Coroutine 종료 + continuation.resumeWithException(t) + } + + override fun onResponse(call: Call, response: Response) { + val result = if (response.isSuccessful) { + response.body()?.let { + ApiResult.Success(it) + } ?: ApiResult.Failure(code = response.code(), message = "Response body is null") + } else { + parseErrorResponse(response) + } + + continuation.resume(result) + } + }) + + // Coroutine이 취소 되면 네트워크 요청도 취소 + continuation.invokeOnCancellation { + call.cancel() + } + } + + emit(apiResult) + } + + private fun parseErrorResponse(response: Response<*>): ApiResult.Failure { + val errorJson = response.errorBody()?.string() + + return runCatching { + val errorBody = gson.fromJson(errorJson, ErrorResponse::class.java) + val message = errorBody?.run { + message ?: error ?: "알 수 없는 에러가 발생하였습니다." + } + + ApiResult.Failure( + code = errorBody.status, + message = message + ) + }.getOrElse { + ApiResult.Failure( + code = response.code(), + message = "알 수 없는 에러가 발생하였습니다." + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapterFactory.kt b/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapterFactory.kt new file mode 100644 index 00000000..e6d160b3 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapterFactory.kt @@ -0,0 +1,44 @@ +package com.runnect.runnect.data.calladapter.flow + +import com.runnect.runnect.data.ApiResult +import kotlinx.coroutines.flow.Flow +import retrofit2.CallAdapter +import retrofit2.Retrofit +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +class FlowCallAdapterFactory private constructor() : CallAdapter.Factory() { + + override fun get( + returnType: Type, + annotations: Array, + retrofit: Retrofit + ): CallAdapter<*, *>? { + // Api Service 메소드의 반환형의 최상위 타입이 Flow 인지 체크 + // suspend가 아니므로 Call 체크 필요 x + if (getRawType(returnType) != Flow::class.java) { + return null + } + + check(returnType is ParameterizedType) { "Flow return type must be parameterized as Flow or Flow" } + + val responseType = getParameterUpperBound(0, returnType) + if (getRawType(responseType) != ApiResult::class.java) { + return null + } + + check(responseType is ParameterizedType) { "ApiResult return type must be parameterized as ApiResult or ApiResult" } + + return FlowApiResultCallAdapter( + getParameterUpperBound( + 0, + responseType + ) + ) + } + + companion object { + @JvmStatic + fun create() = FlowCallAdapterFactory() + } +} \ No newline at end of file From 638452e591886e4470f846e94cd66b6091e49320 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:36:57 +0900 Subject: [PATCH 02/23] =?UTF-8?q?[feat]=20:=20ResponseInterceptor=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 서버에서 BaseResponse 형태로 수신되고 있어서 불필요한 래핑 발생 * data 외의 값은 쓰지 않고 있어서 Interceptor를 통해 data만 추출 --- .../data/interceptor/ResponseInterceptor.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/data/interceptor/ResponseInterceptor.kt diff --git a/app/src/main/java/com/runnect/runnect/data/interceptor/ResponseInterceptor.kt b/app/src/main/java/com/runnect/runnect/data/interceptor/ResponseInterceptor.kt new file mode 100644 index 00000000..45088923 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/interceptor/ResponseInterceptor.kt @@ -0,0 +1,55 @@ +package com.runnect.runnect.data.interceptor + +import com.google.gson.Gson +import com.google.gson.JsonParseException +import com.google.gson.JsonSyntaxException +import com.runnect.runnect.data.dto.response.base.BaseResponse +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import timber.log.Timber + +/** + * BaseResponse에서 data만 추출 (불필요한 래핑 제거) + * 서버에서 내려준 형식이 아니라면 응답 그대로 반환 + */ +class ResponseInterceptor : Interceptor { + + private val gson = Gson() + + override fun intercept(chain: Interceptor.Chain): Response { + val originalResponse = chain.proceed(chain.request()) + if (!originalResponse.isSuccessful) return originalResponse + + val bodyString = originalResponse.peekBody(Long.MAX_VALUE).string() + val newResponseBodyString = jsonToBaseResponse(bodyString)?.let { + it.toResponseBody("application/json".toMediaTypeOrNull()) + } ?: return originalResponse + + return originalResponse.newBuilder() + .code(originalResponse.code) + .body(newResponseBodyString) + .build() + .apply { + Timber.v("""\n + origin = ${originalResponse.peekBody(Long.MAX_VALUE).string()} + new = ${this.peekBody(Long.MAX_VALUE).string()} + """.trimIndent() + ) + } + } + + private fun jsonToBaseResponse(body: String): String? { + return try { + val baseResponse = gson.fromJson(body, BaseResponse::class.java) + gson.toJson(baseResponse.data) + } catch (e: JsonSyntaxException) { + null // JSON 구문 분석 오류 발생 시 원래 형식을 반환 + } catch (e: JsonParseException) { + null // JSON 파싱 오류 발생 시 원래 형식을 반환 + } catch (e: Exception) { + null // 기타 예외 발생 시 원래 형식을 반환 + } + } +} \ No newline at end of file From b37961c55bcb43da2edfe5de57c5e5603dcc6e11 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:38:29 +0900 Subject: [PATCH 03/23] =?UTF-8?q?[feat]=20:=20ErrorResponse=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 에러가 발생했을 때 러넥트 서버 에러 + 기본 에러를 같이 받을 수 있도록 처리 --- .../data/dto/response/base/ErrorResponse.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/data/dto/response/base/ErrorResponse.kt diff --git a/app/src/main/java/com/runnect/runnect/data/dto/response/base/ErrorResponse.kt b/app/src/main/java/com/runnect/runnect/data/dto/response/base/ErrorResponse.kt new file mode 100644 index 00000000..5d0557fb --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/dto/response/base/ErrorResponse.kt @@ -0,0 +1,18 @@ +package com.runnect.runnect.data.dto.response.base + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ErrorResponse( + @SerialName("status") + val status: Int, + @SerialName("success") + val success: Boolean?, + @SerialName("message") + val message: String?, + @SerialName("error") + val error: String?, + @SerialName("data") + val data: T? = null +) From 6583f45572379faf63261f0b138f3d887707e9b1 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:39:22 +0900 Subject: [PATCH 04/23] =?UTF-8?q?[feat]=20:=20ApiResult=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/runnect/runnect/data/ApiResult.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/data/ApiResult.kt diff --git a/app/src/main/java/com/runnect/runnect/data/ApiResult.kt b/app/src/main/java/com/runnect/runnect/data/ApiResult.kt new file mode 100644 index 00000000..c8e288bc --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/ApiResult.kt @@ -0,0 +1,51 @@ +package com.runnect.runnect.data + +import com.runnect.runnect.domain.common.Result +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map + +sealed class ApiResult { + data class Success(val body: R) : ApiResult() + data class Failure( + val code: Int, + val message: String?, + ) : ApiResult() + + // ApiResult 형태를 DomainResult 형태로 매핑 + fun mapToResult(mapper: (R) -> D): Result { + return when (this) { + is Success -> { + Result.Success(mapper(this.body)) + } + + is Failure -> { + Result.Failure(code, message) + } + } + } + + fun mapToFlowResult(mapper: (R) -> D): Flow> { + return flow { + when (this@ApiResult) { + is Success -> { + emit( + Result.Success(mapper(body)) + ) + } + + is Failure -> { + emit( + Result.Failure(code, message) + ) + } + } + } + } +} + +fun Flow>.mapToResult(mapper: (R) -> D): Flow> { + return this.map { + it.mapToResult(mapper) + } +} \ No newline at end of file From c023bd77ef8823786e60d921b0210632485e4929 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:39:34 +0900 Subject: [PATCH 05/23] =?UTF-8?q?[feat]=20:=20Domain=20Result=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/runnect/domain/common/Result.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/domain/common/Result.kt diff --git a/app/src/main/java/com/runnect/runnect/domain/common/Result.kt b/app/src/main/java/com/runnect/runnect/domain/common/Result.kt new file mode 100644 index 00000000..f2a98b24 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/domain/common/Result.kt @@ -0,0 +1,32 @@ +package com.runnect.runnect.domain.common + +sealed interface Result { + data class Success(val data: T) : Result + data class Failure( + val code: Int, + val message: String?, + ) : Result + + fun isSuccess(): Boolean = this is Success + fun isFailure(): Boolean = this is Failure + + fun getOrNull(): T? = when (this) { + is Success -> data + is Failure -> null + } + + fun exceptionOrNull(): Throwable? = when (this) { + is Success -> null + is Failure -> RunnectException(code, message) + } +} + +fun Result.onSuccess(action: (T) -> Unit): Result { + if (this is Result.Success) action(data) + return this +} + +fun Result.onFailure(action: (RunnectException) -> Unit): Result { + if (this is Result.Failure) action(RunnectException(code, message)) + return this +} \ No newline at end of file From 9cf1e0117562ac983778aaa737345753f911b656 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:40:04 +0900 Subject: [PATCH 06/23] =?UTF-8?q?[feat]=20:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EC=9A=94=EC=B2=AD=EC=97=90=EC=84=9C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=9C=20=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=B4=20RunnectException=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/runnect/domain/common/RunnectException.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/domain/common/RunnectException.kt diff --git a/app/src/main/java/com/runnect/runnect/domain/common/RunnectException.kt b/app/src/main/java/com/runnect/runnect/domain/common/RunnectException.kt new file mode 100644 index 00000000..81823624 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/domain/common/RunnectException.kt @@ -0,0 +1,9 @@ +package com.runnect.runnect.domain.common + +class RunnectException( + val code: Int, + override val message: String? +) : Throwable(message) { + + fun toLog() = "$message(${code})" +} \ No newline at end of file From 48f09388d06cf6a633acce75883377f3d2953944 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:44:19 +0900 Subject: [PATCH 07/23] =?UTF-8?q?[feat]=20:=20V2=20Retrofit,=20OkHttpClien?= =?UTF-8?q?t=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/runnect/runnect/di/RetrofitModule.kt | 75 +++++++++++++++++-- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt index 43ba1d33..81a63e61 100644 --- a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt +++ b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt @@ -3,7 +3,8 @@ package com.runnect.runnect.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.runnect.runnect.BuildConfig import com.runnect.runnect.application.ApplicationClass -import com.runnect.runnect.application.PreferenceManager +import com.runnect.runnect.data.calladapter.flow.FlowCallAdapterFactory +import com.runnect.runnect.data.interceptor.ResponseInterceptor import com.runnect.runnect.data.service.* import com.runnect.runnect.data.repository.* import com.runnect.runnect.data.source.remote.* @@ -20,6 +21,7 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Qualifier import javax.inject.Singleton @@ -30,18 +32,49 @@ object RetrofitModule { @Retention(AnnotationRetention.BINARY) annotation class Runnect + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class RetrofitV2 + @Qualifier @Retention(AnnotationRetention.BINARY) annotation class Tmap + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class HttpClient + + @Qualifier + @Retention(AnnotationRetention.BINARY) + annotation class HttpClientV2 + @Provides @Singleton + @HttpClient fun provideOkHttpClient( logger: HttpLoggingInterceptor, appInterceptor: AppInterceptor, tokenAuthenticator: TokenAuthenticator - ): OkHttpClient = OkHttpClient.Builder().addInterceptor(logger).addInterceptor(appInterceptor) - .authenticator(tokenAuthenticator).build() + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(logger) + .addInterceptor(appInterceptor) + .authenticator(tokenAuthenticator) + .build() + + @Provides + @Singleton + @HttpClientV2 + fun provideOkHttpClientV2( + logger: HttpLoggingInterceptor, + appInterceptor: AppInterceptor, + responseInterceptor: ResponseInterceptor, + tokenAuthenticator: TokenAuthenticator + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(logger) + .addInterceptor(appInterceptor) + .addInterceptor(responseInterceptor) + .authenticator(tokenAuthenticator) + .build() @Provides @Singleton @@ -53,6 +86,10 @@ object RetrofitModule { @Singleton fun provideAppInterceptor(): AppInterceptor = AppInterceptor() + @Provides + @Singleton + fun provideResponseInterceptor(): ResponseInterceptor = ResponseInterceptor() + @Provides @Singleton fun provideTokenAuthenticator(): TokenAuthenticator = @@ -62,21 +99,43 @@ object RetrofitModule { @Provides @Singleton @Runnect - fun provideRunnectRetrofit(json: Json, client: OkHttpClient): Retrofit { + fun provideRunnectRetrofit(json: Json, @HttpClient client: OkHttpClient): Retrofit { kotlinx.coroutines.internal.synchronized(this) { val baseUrl = ApplicationClass.getBaseUrl() - val retrofit = Retrofit.Builder().baseUrl(baseUrl).client(client) - .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) - .build() + val retrofit = Retrofit.Builder() + .baseUrl(baseUrl) + .client(client) + .addConverterFactory( + json.asConverterFactory("application/json".toMediaType()) + ).build() + return retrofit ?: throw RuntimeException("Retrofit creation failed.") } } + @OptIn(ExperimentalSerializationApi::class, InternalCoroutinesApi::class) + @Provides + @Singleton + @RetrofitV2 + fun provideRunnectRetrofitV2( + @HttpClientV2 client: OkHttpClient + ): Retrofit { + val baseUrl = ApplicationClass.getBaseUrl() + val retrofit = Retrofit.Builder() + .baseUrl(baseUrl) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(FlowCallAdapterFactory.create()) + .build() + + return retrofit ?: throw RuntimeException("Retrofit creation failed.") + } + @OptIn(ExperimentalSerializationApi::class, InternalCoroutinesApi::class) @Provides @Singleton @Tmap - fun provideTmapRetrofit(json: Json, client: OkHttpClient): Retrofit { + fun provideTmapRetrofit(json: Json, @HttpClient client: OkHttpClient): Retrofit { kotlinx.coroutines.internal.synchronized(this) { val retrofit = Retrofit.Builder().baseUrl(BuildConfig.TMAP_BASE_URL).client(client) .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) From 5d2a745181d56f92eae7262454dce50c71c4f636 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:44:39 +0900 Subject: [PATCH 08/23] =?UTF-8?q?[refacor]=20:=20=EB=A7=88=EB=9D=BC?= =?UTF-8?q?=ED=86=A4=20=EC=BD=94=EC=8A=A4=20=EC=A1=B0=ED=9A=8C=20V2?= =?UTF-8?q?=EB=A1=9C=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/repository/CourseRepositoryImpl.kt | 22 +++++++++------ .../runnect/data/service/CourseService.kt | 3 --- .../runnect/data/service/CourseV2Service.kt | 11 ++++++++ .../source/remote/RemoteCourseDataSource.kt | 8 ++++-- .../com/runnect/runnect/di/ServiceModule.kt | 6 +++++ .../domain/repository/CourseRepository.kt | 16 ++++++----- .../discover/DiscoverViewModel.kt | 27 +++++++++---------- 7 files changed, 59 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt index 58e4839a..fd596ab6 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt @@ -12,13 +12,16 @@ import com.runnect.runnect.data.dto.response.ResponsePostMyHistory import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostDiscoverUpload import com.runnect.runnect.data.dto.response.ResponsePostScrap +import com.runnect.runnect.data.mapToResult import com.runnect.runnect.data.source.remote.RemoteCourseDataSource +import com.runnect.runnect.domain.common.Result import com.runnect.runnect.domain.entity.DiscoverSearchCourse import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.* import com.runnect.runnect.domain.entity.DiscoverUploadCourse import com.runnect.runnect.domain.entity.EditableCourseDetail import com.runnect.runnect.domain.entity.RecommendCoursePagingData import com.runnect.runnect.domain.repository.CourseRepository +import kotlinx.coroutines.flow.Flow import okhttp3.MultipartBody import okhttp3.RequestBody import retrofit2.Response @@ -26,14 +29,17 @@ import javax.inject.Inject class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSource: RemoteCourseDataSource) : CourseRepository { - override suspend fun getMarathonCourse(): Result?> = runCatching { - remoteCourseDataSource.getMarathonCourse().data?.toMarathonCourses() + + override suspend fun getMarathonCourse(): Flow>> { + return remoteCourseDataSource.getMarathonCourse().mapToResult { + it.toMarathonCourses() + } } override suspend fun getRecommendCourse( pageNo: String, sort: String - ): Result = runCatching { + ): kotlin.Result = runCatching { val response = remoteCourseDataSource.getRecommendCourse( pageNo = pageNo, sort = sort @@ -44,12 +50,12 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc } } - override suspend fun getCourseSearch(keyword: String): Result?> = + override suspend fun getCourseSearch(keyword: String): kotlin.Result?> = runCatching { remoteCourseDataSource.getCourseSearch(keyword = keyword).data?.toDiscoverSearchCourses() } - override suspend fun getMyCourseLoad(): Result?> = + override suspend fun getMyCourseLoad(): kotlin.Result?> = runCatching { remoteCourseDataSource.getMyCourseLoad().data?.toUploadCourses() } @@ -82,14 +88,14 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun getCourseDetail( publicCourseId: Int - ): Result = runCatching { + ): kotlin.Result = runCatching { remoteCourseDataSource.getCourseDetail(publicCourseId = publicCourseId).data?.toCourseDetail() } override suspend fun patchPublicCourse( publicCourseId: Int, requestPatchPublicCourse: RequestPatchPublicCourse - ): Result = runCatching { + ): kotlin.Result = runCatching { remoteCourseDataSource.patchPublicCourse( publicCourseId = publicCourseId, requestPatchPublicCourse = requestPatchPublicCourse @@ -98,7 +104,7 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun postCourseScrap( requestPostCourseScrap: RequestPostCourseScrap - ): Result = runCatching { + ): kotlin.Result = runCatching { remoteCourseDataSource.postCourseScrap(requestPostCourseScrap = requestPostCourseScrap).data } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt index 2f3dc91e..fa8d8552 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseService.kt @@ -6,7 +6,6 @@ import com.runnect.runnect.data.dto.request.RequestPostPublicCourse import com.runnect.runnect.data.dto.request.RequestPostRunningHistory import com.runnect.runnect.data.dto.request.RequestPutMyDrawCourse import com.runnect.runnect.data.dto.response.ResponseGetCourseDetail -import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon import com.runnect.runnect.data.dto.response.ResponseGetDiscoverRecommend import com.runnect.runnect.data.dto.response.ResponseGetDiscoverSearch import com.runnect.runnect.data.dto.response.ResponseGetDiscoverUploadCourse @@ -26,8 +25,6 @@ import retrofit2.Response import retrofit2.http.* interface CourseService { - @GET("/api/public-course/marathon") - suspend fun getMarathonCourse(): BaseResponse @GET("/api/public-course") suspend fun getRecommendCourse( diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt new file mode 100644 index 00000000..88404cbc --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt @@ -0,0 +1,11 @@ +package com.runnect.runnect.data.service + +import com.runnect.runnect.data.ApiResult +import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon +import kotlinx.coroutines.flow.Flow +import retrofit2.http.GET + +interface CourseV2Service { + @GET("/api/public-course/ma12rathon") + fun getMarathonCourse(): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt index 2f1b6112..37bf73a6 100644 --- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt +++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt @@ -1,5 +1,6 @@ package com.runnect.runnect.data.source.remote +import com.runnect.runnect.data.ApiResult import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse import com.runnect.runnect.data.dto.request.RequestPostCourseScrap import com.runnect.runnect.data.dto.request.RequestPostPublicCourse @@ -12,15 +13,18 @@ import com.runnect.runnect.data.dto.response.ResponsePatchPublicCourse import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.data.dto.response.base.BaseResponse import com.runnect.runnect.data.service.CourseService +import com.runnect.runnect.data.service.CourseV2Service +import kotlinx.coroutines.flow.Flow import okhttp3.MultipartBody import okhttp3.RequestBody import javax.inject.Inject class RemoteCourseDataSource @Inject constructor( + private var courseV2Service: CourseV2Service, private val courseService: CourseService ) { - suspend fun getMarathonCourse(): BaseResponse = - courseService.getMarathonCourse() + fun getMarathonCourse(): Flow> = + courseV2Service.getMarathonCourse() suspend fun getRecommendCourse( pageNo: String, diff --git a/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt b/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt index bcee7268..01ea70b5 100644 --- a/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt +++ b/app/src/main/java/com/runnect/runnect/di/ServiceModule.kt @@ -16,6 +16,12 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object ServiceModule { + + @Singleton + @Provides + fun providePCourseV2Service(@RetrofitModule.RetrofitV2 retrofitV2: Retrofit) = + retrofitV2.create(CourseV2Service::class.java) + @Singleton @Provides fun providePCourseService(@RetrofitModule.Runnect runnectRetrofit: Retrofit) = diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt index add0e3d3..915fc967 100644 --- a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt +++ b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt @@ -11,27 +11,29 @@ import com.runnect.runnect.data.dto.response.ResponsePostMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostMyHistory import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse +import com.runnect.runnect.domain.common.Result import com.runnect.runnect.domain.entity.CourseDetail import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse import com.runnect.runnect.domain.entity.DiscoverSearchCourse import com.runnect.runnect.domain.entity.DiscoverUploadCourse import com.runnect.runnect.domain.entity.EditableCourseDetail import com.runnect.runnect.domain.entity.RecommendCoursePagingData +import kotlinx.coroutines.flow.Flow import okhttp3.MultipartBody import okhttp3.RequestBody import retrofit2.Response interface CourseRepository { - suspend fun getMarathonCourse(): Result?> + suspend fun getMarathonCourse(): Flow>> suspend fun getRecommendCourse( pageNo: String, sort: String - ): Result + ): kotlin.Result - suspend fun getCourseSearch(keyword: String): Result?> + suspend fun getCourseSearch(keyword: String): kotlin.Result?> - suspend fun getMyCourseLoad(): Result?> + suspend fun getMyCourseLoad(): kotlin.Result?> suspend fun postUploadMyCourse(requestPostPublicCourse: RequestPostPublicCourse): ResponsePostDiscoverUpload @@ -45,12 +47,12 @@ interface CourseRepository { image: MultipartBody.Part, data: RequestBody ): Response - suspend fun getCourseDetail(publicCourseId: Int): Result + suspend fun getCourseDetail(publicCourseId: Int): kotlin.Result suspend fun patchPublicCourse( publicCourseId: Int, requestPatchPublicCourse: RequestPatchPublicCourse - ): Result + ): kotlin.Result - suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): Result + suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): kotlin.Result } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt index f7462451..68dc18cd 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt @@ -6,13 +6,18 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.runnect.runnect.data.dto.request.RequestPostCourseScrap import com.runnect.runnect.data.dto.response.ResponsePostScrap +import com.runnect.runnect.domain.common.onFailure +import com.runnect.runnect.domain.common.onSuccess import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.* import com.runnect.runnect.domain.entity.DiscoverBanner import com.runnect.runnect.domain.repository.BannerRepository import com.runnect.runnect.domain.repository.CourseRepository import com.runnect.runnect.presentation.state.UiStateV2 import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -98,22 +103,16 @@ class DiscoverViewModel @Inject constructor( private fun getMarathonCourses() { viewModelScope.launch { - _marathonCourseGetState.value = UiStateV2.Loading - courseRepository.getMarathonCourse() - .onSuccess { courses -> - if (courses == null) { - _marathonCourseGetState.value = - UiStateV2.Failure("MARATHON COURSE DATA IS NULL") - return@launch + .flowOn(Dispatchers.IO) + .onStart { + _marathonCourseGetState.value = UiStateV2.Loading + }.collect { result -> + result.onSuccess { + _marathonCourseGetState.value = UiStateV2.Success(it) + }.onFailure { + _marathonCourseGetState.value = UiStateV2.Failure(it.toLog()) } - - _marathonCourseGetState.value = UiStateV2.Success(courses) - Timber.d("MARATHON COURSE GET SUCCESS") - } - .onFailure { exception -> - _marathonCourseGetState.value = UiStateV2.Failure(exception.message.toString()) - Timber.e("MARATHON COURSE GET FAIL") } } } From 40ed534a3c629723dd0f82792d8768bf5af9bddd Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 14:45:18 +0900 Subject: [PATCH 09/23] =?UTF-8?q?[del]=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=9B=90=EB=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/runnect/runnect/data/service/CourseV2Service.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt index 88404cbc..2171ec8b 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt @@ -6,6 +6,6 @@ import kotlinx.coroutines.flow.Flow import retrofit2.http.GET interface CourseV2Service { - @GET("/api/public-course/ma12rathon") + @GET("/api/public-course/marathon") fun getMarathonCourse(): Flow> } \ No newline at end of file From f93a152aeeb17ced480f0bca70bfd94d55fa4273 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Fri, 22 Mar 2024 15:04:57 +0900 Subject: [PATCH 10/23] =?UTF-8?q?[refactor]=20:=20=EC=BD=94=EB=93=9C,=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/runnect/runnect/data/ApiResult.kt | 51 ------------------- .../runnect/runnect/data/network/ApiResult.kt | 33 ++++++++++++ .../calladapter/FlowApiResultCallAdapter.kt} | 4 +- .../calladapter}/FlowCallAdapterFactory.kt | 4 +- .../interceptor/ResponseInterceptor.kt | 4 +- .../data/repository/CourseRepositoryImpl.kt | 2 +- .../runnect/data/service/CourseV2Service.kt | 2 +- .../source/remote/RemoteCourseDataSource.kt | 2 +- .../com/runnect/runnect/di/RetrofitModule.kt | 4 +- 9 files changed, 44 insertions(+), 62 deletions(-) delete mode 100644 app/src/main/java/com/runnect/runnect/data/ApiResult.kt create mode 100644 app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt rename app/src/main/java/com/runnect/runnect/data/{calladapter/flow/FlowCallAdapter.kt => network/calladapter/FlowApiResultCallAdapter.kt} (96%) rename app/src/main/java/com/runnect/runnect/data/{calladapter/flow => network/calladapter}/FlowCallAdapterFactory.kt (92%) rename app/src/main/java/com/runnect/runnect/data/{ => network}/interceptor/ResponseInterceptor.kt (93%) diff --git a/app/src/main/java/com/runnect/runnect/data/ApiResult.kt b/app/src/main/java/com/runnect/runnect/data/ApiResult.kt deleted file mode 100644 index c8e288bc..00000000 --- a/app/src/main/java/com/runnect/runnect/data/ApiResult.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.runnect.runnect.data - -import com.runnect.runnect.domain.common.Result -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map - -sealed class ApiResult { - data class Success(val body: R) : ApiResult() - data class Failure( - val code: Int, - val message: String?, - ) : ApiResult() - - // ApiResult 형태를 DomainResult 형태로 매핑 - fun mapToResult(mapper: (R) -> D): Result { - return when (this) { - is Success -> { - Result.Success(mapper(this.body)) - } - - is Failure -> { - Result.Failure(code, message) - } - } - } - - fun mapToFlowResult(mapper: (R) -> D): Flow> { - return flow { - when (this@ApiResult) { - is Success -> { - emit( - Result.Success(mapper(body)) - ) - } - - is Failure -> { - emit( - Result.Failure(code, message) - ) - } - } - } - } -} - -fun Flow>.mapToResult(mapper: (R) -> D): Flow> { - return this.map { - it.mapToResult(mapper) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt b/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt new file mode 100644 index 00000000..80bf6263 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt @@ -0,0 +1,33 @@ +package com.runnect.runnect.data.network + +import com.runnect.runnect.domain.common.Result +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map + +sealed class ApiResult { + data class Success(val body: R) : ApiResult() + data class Failure( + val code: Int, + val message: String?, + ) : ApiResult() + + // ApiResult 형태를 DomainResult 형태로 매핑 + fun mapToResult(mapper: (R) -> D): Result = when (this@ApiResult) { + is Success -> Result.Success(mapper(body)) + is Failure -> Result.Failure(code, message) + } + + fun mapToFlowResult(mapper: (R) -> D): Flow> = flow { + emit( + when (this@ApiResult) { + is Success -> Result.Success(mapper(body)) + is Failure -> Result.Failure(code, message) + } + ) + } +} + +fun Flow>.mapToResult(mapper: (R) -> D): Flow> { + return map { it.mapToResult(mapper) } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowApiResultCallAdapter.kt similarity index 96% rename from app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapter.kt rename to app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowApiResultCallAdapter.kt index ccf709e0..540719d6 100644 --- a/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowApiResultCallAdapter.kt @@ -1,7 +1,7 @@ -package com.runnect.runnect.data.calladapter.flow +package com.runnect.runnect.data.network.calladapter import com.google.gson.Gson -import com.runnect.runnect.data.ApiResult +import com.runnect.runnect.data.network.ApiResult import com.runnect.runnect.data.dto.response.base.ErrorResponse import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow diff --git a/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapterFactory.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowCallAdapterFactory.kt similarity index 92% rename from app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapterFactory.kt rename to app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowCallAdapterFactory.kt index e6d160b3..41461acb 100644 --- a/app/src/main/java/com/runnect/runnect/data/calladapter/flow/FlowCallAdapterFactory.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowCallAdapterFactory.kt @@ -1,6 +1,6 @@ -package com.runnect.runnect.data.calladapter.flow +package com.runnect.runnect.data.network.calladapter -import com.runnect.runnect.data.ApiResult +import com.runnect.runnect.data.network.ApiResult import kotlinx.coroutines.flow.Flow import retrofit2.CallAdapter import retrofit2.Retrofit diff --git a/app/src/main/java/com/runnect/runnect/data/interceptor/ResponseInterceptor.kt b/app/src/main/java/com/runnect/runnect/data/network/interceptor/ResponseInterceptor.kt similarity index 93% rename from app/src/main/java/com/runnect/runnect/data/interceptor/ResponseInterceptor.kt rename to app/src/main/java/com/runnect/runnect/data/network/interceptor/ResponseInterceptor.kt index 45088923..256e2ec7 100644 --- a/app/src/main/java/com/runnect/runnect/data/interceptor/ResponseInterceptor.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/interceptor/ResponseInterceptor.kt @@ -1,4 +1,4 @@ -package com.runnect.runnect.data.interceptor +package com.runnect.runnect.data.network.interceptor import com.google.gson.Gson import com.google.gson.JsonParseException @@ -12,7 +12,7 @@ import timber.log.Timber /** * BaseResponse에서 data만 추출 (불필요한 래핑 제거) - * 서버에서 내려준 형식이 아니라면 응답 그대로 반환 + * - 서버에서 내려준 형식이 아니라면 응답 그대로 반환 */ class ResponseInterceptor : Interceptor { diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt index fd596ab6..c7758767 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt @@ -12,7 +12,7 @@ import com.runnect.runnect.data.dto.response.ResponsePostMyHistory import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostDiscoverUpload import com.runnect.runnect.data.dto.response.ResponsePostScrap -import com.runnect.runnect.data.mapToResult +import com.runnect.runnect.data.network.mapToResult import com.runnect.runnect.data.source.remote.RemoteCourseDataSource import com.runnect.runnect.domain.common.Result import com.runnect.runnect.domain.entity.DiscoverSearchCourse diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt index 2171ec8b..d6bb705f 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt @@ -1,6 +1,6 @@ package com.runnect.runnect.data.service -import com.runnect.runnect.data.ApiResult +import com.runnect.runnect.data.network.ApiResult import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon import kotlinx.coroutines.flow.Flow import retrofit2.http.GET diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt index 37bf73a6..af50fb78 100644 --- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt +++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt @@ -1,6 +1,6 @@ package com.runnect.runnect.data.source.remote -import com.runnect.runnect.data.ApiResult +import com.runnect.runnect.data.network.ApiResult import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse import com.runnect.runnect.data.dto.request.RequestPostCourseScrap import com.runnect.runnect.data.dto.request.RequestPostPublicCourse diff --git a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt index 81a63e61..342d6e58 100644 --- a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt +++ b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt @@ -3,8 +3,8 @@ package com.runnect.runnect.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.runnect.runnect.BuildConfig import com.runnect.runnect.application.ApplicationClass -import com.runnect.runnect.data.calladapter.flow.FlowCallAdapterFactory -import com.runnect.runnect.data.interceptor.ResponseInterceptor +import com.runnect.runnect.data.network.calladapter.FlowCallAdapterFactory +import com.runnect.runnect.data.network.interceptor.ResponseInterceptor import com.runnect.runnect.data.service.* import com.runnect.runnect.data.repository.* import com.runnect.runnect.data.source.remote.* From ed7be152d707106a64d8652b4b36cd77b57bf30d Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 12:27:14 +0900 Subject: [PATCH 11/23] =?UTF-8?q?[feat]=20BaseViewModel(+=20exceptionHandl?= =?UTF-8?q?er)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/base/BaseViewModel.kt | 41 +++++++++++++++++++ .../discover/DiscoverViewModel.kt | 5 ++- 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/runnect/runnect/presentation/base/BaseViewModel.kt diff --git a/app/src/main/java/com/runnect/runnect/presentation/base/BaseViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/base/BaseViewModel.kt new file mode 100644 index 00000000..55119415 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/presentation/base/BaseViewModel.kt @@ -0,0 +1,41 @@ +package com.runnect.runnect.presentation.base + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import retrofit2.HttpException +import timber.log.Timber +import java.net.SocketException +import java.net.UnknownHostException + +abstract class BaseViewModel : ViewModel() { + + open val exceptionHandler = CoroutineExceptionHandler { _, throwable -> + Timber.tag(throwable::class.java.simpleName).e(throwable) + + when (throwable) { + is SocketException, + is HttpException, + is UnknownHostException -> { + Timber.e(throwable) + } + else -> { + Timber.e(throwable) + } + } + } + + fun ViewModel.launchWithHandler( + dispatcher: CoroutineDispatcher = Dispatchers.Main, + exceptionHandler: CoroutineExceptionHandler = this@BaseViewModel.exceptionHandler, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit + ) { + viewModelScope.launch(dispatcher + exceptionHandler, start, block) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt index 68dc18cd..b0cdb22e 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt @@ -12,6 +12,7 @@ import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.* import com.runnect.runnect.domain.entity.DiscoverBanner import com.runnect.runnect.domain.repository.BannerRepository import com.runnect.runnect.domain.repository.CourseRepository +import com.runnect.runnect.presentation.base.BaseViewModel import com.runnect.runnect.presentation.state.UiStateV2 import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -26,7 +27,7 @@ import javax.inject.Inject class DiscoverViewModel @Inject constructor( private val courseRepository: CourseRepository, private val bannerRepository: BannerRepository -) : ViewModel() { +) : BaseViewModel() { private val _bannerGetState = MutableLiveData>>() val bannerGetState: LiveData>> get() = _bannerGetState @@ -102,7 +103,7 @@ class DiscoverViewModel @Inject constructor( } private fun getMarathonCourses() { - viewModelScope.launch { + launchWithHandler { courseRepository.getMarathonCourse() .flowOn(Dispatchers.IO) .onStart { From decd00e7c53a2c4a452c98b28f99976d08c6c9e8 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 14:12:15 +0900 Subject: [PATCH 12/23] =?UTF-8?q?[del]=20FlowCallAdapter=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calladapter/FlowApiResultCallAdapter.kt | 76 ------------------- .../calladapter/FlowCallAdapterFactory.kt | 44 ----------- 2 files changed, 120 deletions(-) delete mode 100644 app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowApiResultCallAdapter.kt delete mode 100644 app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowCallAdapterFactory.kt diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowApiResultCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowApiResultCallAdapter.kt deleted file mode 100644 index 540719d6..00000000 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowApiResultCallAdapter.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.runnect.runnect.data.network.calladapter - -import com.google.gson.Gson -import com.runnect.runnect.data.network.ApiResult -import com.runnect.runnect.data.dto.response.base.ErrorResponse -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.suspendCancellableCoroutine -import retrofit2.Call -import retrofit2.CallAdapter -import retrofit2.Callback -import retrofit2.Response -import java.lang.reflect.Type -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException - -class FlowApiResultCallAdapter( - private val responseType: Type -) : CallAdapter>> { - - private val gson = Gson() - - override fun responseType() = responseType - - // Retrofit의 Call을 Flow<>로 변환 - override fun adapt(call: Call): Flow> = flow { - val apiResult = suspendCancellableCoroutine { continuation -> - call.enqueue(object : Callback { - override fun onFailure(call: Call, t: Throwable) { - // 예외 발생시키고 Coroutine 종료 - continuation.resumeWithException(t) - } - - override fun onResponse(call: Call, response: Response) { - val result = if (response.isSuccessful) { - response.body()?.let { - ApiResult.Success(it) - } ?: ApiResult.Failure(code = response.code(), message = "Response body is null") - } else { - parseErrorResponse(response) - } - - continuation.resume(result) - } - }) - - // Coroutine이 취소 되면 네트워크 요청도 취소 - continuation.invokeOnCancellation { - call.cancel() - } - } - - emit(apiResult) - } - - private fun parseErrorResponse(response: Response<*>): ApiResult.Failure { - val errorJson = response.errorBody()?.string() - - return runCatching { - val errorBody = gson.fromJson(errorJson, ErrorResponse::class.java) - val message = errorBody?.run { - message ?: error ?: "알 수 없는 에러가 발생하였습니다." - } - - ApiResult.Failure( - code = errorBody.status, - message = message - ) - }.getOrElse { - ApiResult.Failure( - code = response.code(), - message = "알 수 없는 에러가 발생하였습니다." - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowCallAdapterFactory.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowCallAdapterFactory.kt deleted file mode 100644 index 41461acb..00000000 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/FlowCallAdapterFactory.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.runnect.runnect.data.network.calladapter - -import com.runnect.runnect.data.network.ApiResult -import kotlinx.coroutines.flow.Flow -import retrofit2.CallAdapter -import retrofit2.Retrofit -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type - -class FlowCallAdapterFactory private constructor() : CallAdapter.Factory() { - - override fun get( - returnType: Type, - annotations: Array, - retrofit: Retrofit - ): CallAdapter<*, *>? { - // Api Service 메소드의 반환형의 최상위 타입이 Flow 인지 체크 - // suspend가 아니므로 Call 체크 필요 x - if (getRawType(returnType) != Flow::class.java) { - return null - } - - check(returnType is ParameterizedType) { "Flow return type must be parameterized as Flow or Flow" } - - val responseType = getParameterUpperBound(0, returnType) - if (getRawType(responseType) != ApiResult::class.java) { - return null - } - - check(responseType is ParameterizedType) { "ApiResult return type must be parameterized as ApiResult or ApiResult" } - - return FlowApiResultCallAdapter( - getParameterUpperBound( - 0, - responseType - ) - ) - } - - companion object { - @JvmStatic - fun create() = FlowCallAdapterFactory() - } -} \ No newline at end of file From 5b1fa8f1082d9cd28847257181a1581e9c98f47a Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 14:12:54 +0900 Subject: [PATCH 13/23] =?UTF-8?q?[feat]=20ApiResultCallAdapter=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/network/calladapter/ApiResultCall.kt | 70 +++++++++++++++++++ .../calladapter/ApiResultCallAdapter.kt | 18 +++++ .../ApiResultCallAdapterFactory.kt | 44 ++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCall.kt create mode 100644 app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapter.kt create mode 100644 app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapterFactory.kt diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCall.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCall.kt new file mode 100644 index 00000000..c2ae341d --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCall.kt @@ -0,0 +1,70 @@ +package com.runnect.runnect.data.network.calladapter + +import com.google.gson.Gson +import com.runnect.runnect.data.dto.response.base.ErrorResponse +import com.runnect.runnect.data.network.ApiResult +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import okhttp3.Request +import okio.Timeout + +class ApiResultCall(private val call: Call) : Call> { + + private val gson = Gson() + + override fun execute(): Response> { + throw UnsupportedOperationException("ResultCall doesn't support execute") + } + + override fun enqueue(callback: Callback>) { + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + val apiResult = if (response.isSuccessful) { + response.body()?.let { + ApiResult.Success(it) + } ?: ApiResult.Failure(code = response.code(), message = "Response body is null") + } else { + parseErrorResponse(response) + } + + callback.onResponse( + this@ApiResultCall, + Response.success(apiResult) + ) + } + + override fun onFailure(call: Call, t: Throwable) { + callback.onFailure(this@ApiResultCall, t) + } + }) + } + + private fun parseErrorResponse(response: Response<*>): ApiResult.Failure { + val errorJson = response.errorBody()?.string() + + return runCatching { + val errorBody = gson.fromJson(errorJson, ErrorResponse::class.java) + val message = errorBody?.run { + message ?: error ?: "알 수 없는 에러가 발생하였습니다." + } + + ApiResult.Failure( + code = errorBody.status, + message = message + ) + }.getOrElse { + ApiResult.Failure( + code = response.code(), + message = "알 수 없는 에러가 발생하였습니다." + ) + } + } + + override fun clone(): Call> = ApiResultCall(call.clone()) + override fun isExecuted(): Boolean = call.isExecuted + override fun cancel() = call.cancel() + override fun isCanceled(): Boolean = call.isCanceled + override fun request(): Request = call.request() + override fun timeout(): Timeout = call.timeout() +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapter.kt new file mode 100644 index 00000000..36a5614d --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapter.kt @@ -0,0 +1,18 @@ +package com.runnect.runnect.data.network.calladapter + +import com.runnect.runnect.data.network.ApiResult +import retrofit2.Call +import retrofit2.CallAdapter +import java.lang.reflect.Type + +class ApiResultCallAdapter( + private val responseType: Type +) : CallAdapter>> { + + override fun responseType() = responseType + + // Retrofit의 Call을 Flow<>로 변환 + override fun adapt(call: Call): Call> { + return ApiResultCall(call) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapterFactory.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapterFactory.kt new file mode 100644 index 00000000..683cc6c1 --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapterFactory.kt @@ -0,0 +1,44 @@ +package com.runnect.runnect.data.network.calladapter + +import com.runnect.runnect.data.network.ApiResult +import retrofit2.Call +import retrofit2.CallAdapter +import retrofit2.Retrofit +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +class ApiResultCallAdapterFactory private constructor() : CallAdapter.Factory() { + + override fun get( + returnType: Type, + annotations: Array, + retrofit: Retrofit + ): CallAdapter<*, *>? { + // suspend로 선언시 Call로 감싸짐 + // 최상위 타입이 Call인지 체크 + if (getRawType(returnType) != Call::class.java) { + return null + } + + check(returnType is ParameterizedType) { "Call return type must be parameterized as Call or Call" } + + val responseType = getParameterUpperBound(0, returnType) + if (getRawType(responseType) != ApiResult::class.java) { + return null + } + + check(responseType is ParameterizedType) { "ApiResult return type must be parameterized as ApiResult or ApiResult" } + + return ApiResultCallAdapter( + getParameterUpperBound( + 0, + responseType + ) + ) + } + + companion object { + @JvmStatic + fun create() = ApiResultCallAdapterFactory() + } +} \ No newline at end of file From 69a5fadb6d16df1af055f9a2b6e81c84f9ae8da7 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 14:13:36 +0900 Subject: [PATCH 14/23] =?UTF-8?q?[feat]=20mapper=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95=20(mapToResult=20->=20mapToFlowR?= =?UTF-8?q?esult)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/runnect/data/network/ApiResult.kt | 16 ++++------------ .../data/repository/CourseRepositoryImpl.kt | 3 +-- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt b/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt index 80bf6263..f71b3a88 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt @@ -3,7 +3,6 @@ package com.runnect.runnect.data.network import com.runnect.runnect.domain.common.Result import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map sealed class ApiResult { data class Success(val body: R) : ApiResult() @@ -12,13 +11,10 @@ sealed class ApiResult { val message: String?, ) : ApiResult() - // ApiResult 형태를 DomainResult 형태로 매핑 - fun mapToResult(mapper: (R) -> D): Result = when (this@ApiResult) { - is Success -> Result.Success(mapper(body)) - is Failure -> Result.Failure(code, message) - } - - fun mapToFlowResult(mapper: (R) -> D): Flow> = flow { + // ApiResult 형태를 Flow> 형태로 매핑 + fun mapToFlowResult( + mapper: (R) -> D + ): Flow> = flow { emit( when (this@ApiResult) { is Success -> Result.Success(mapper(body)) @@ -26,8 +22,4 @@ sealed class ApiResult { } ) } -} - -fun Flow>.mapToResult(mapper: (R) -> D): Flow> { - return map { it.mapToResult(mapper) } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt index c7758767..9bdb76de 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt @@ -12,7 +12,6 @@ import com.runnect.runnect.data.dto.response.ResponsePostMyHistory import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostDiscoverUpload import com.runnect.runnect.data.dto.response.ResponsePostScrap -import com.runnect.runnect.data.network.mapToResult import com.runnect.runnect.data.source.remote.RemoteCourseDataSource import com.runnect.runnect.domain.common.Result import com.runnect.runnect.domain.entity.DiscoverSearchCourse @@ -31,7 +30,7 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc CourseRepository { override suspend fun getMarathonCourse(): Flow>> { - return remoteCourseDataSource.getMarathonCourse().mapToResult { + return remoteCourseDataSource.getMarathonCourse().mapToFlowResult { it.toMarathonCourses() } } From 03394c0ebe5868cc35b34175b79916f20b58cbbb Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 14:14:08 +0900 Subject: [PATCH 15/23] =?UTF-8?q?[feat]=20Retrofit=20V2=EC=97=90=20ApiResu?= =?UTF-8?q?ltCallAdapter=20=ED=95=A0=EB=8B=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt index 342d6e58..a2837336 100644 --- a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt +++ b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt @@ -3,7 +3,7 @@ package com.runnect.runnect.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.runnect.runnect.BuildConfig import com.runnect.runnect.application.ApplicationClass -import com.runnect.runnect.data.network.calladapter.FlowCallAdapterFactory +import com.runnect.runnect.data.network.calladapter.ApiResultCallAdapterFactory import com.runnect.runnect.data.network.interceptor.ResponseInterceptor import com.runnect.runnect.data.service.* import com.runnect.runnect.data.repository.* @@ -125,7 +125,7 @@ object RetrofitModule { .baseUrl(baseUrl) .client(client) .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(FlowCallAdapterFactory.create()) + .addCallAdapterFactory(ApiResultCallAdapterFactory.create()) .build() return retrofit ?: throw RuntimeException("Retrofit creation failed.") From 6751341aaa3f3a0d80ae72de49e80cad3c8d07c8 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 14:14:25 +0900 Subject: [PATCH 16/23] =?UTF-8?q?[feat]=20service=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20suspend=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/runnect/runnect/data/service/CourseV2Service.kt | 2 +- .../runnect/data/source/remote/RemoteCourseDataSource.kt | 3 +-- .../runnect/presentation/discover/DiscoverViewModel.kt | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt index d6bb705f..509cf24f 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt @@ -7,5 +7,5 @@ import retrofit2.http.GET interface CourseV2Service { @GET("/api/public-course/marathon") - fun getMarathonCourse(): Flow> + suspend fun getMarathonCourse(): ApiResult } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt index af50fb78..4b33888d 100644 --- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt +++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt @@ -14,7 +14,6 @@ import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.data.dto.response.base.BaseResponse import com.runnect.runnect.data.service.CourseService import com.runnect.runnect.data.service.CourseV2Service -import kotlinx.coroutines.flow.Flow import okhttp3.MultipartBody import okhttp3.RequestBody import javax.inject.Inject @@ -23,7 +22,7 @@ class RemoteCourseDataSource @Inject constructor( private var courseV2Service: CourseV2Service, private val courseService: CourseService ) { - fun getMarathonCourse(): Flow> = + suspend fun getMarathonCourse(): ApiResult = courseV2Service.getMarathonCourse() suspend fun getRecommendCourse( diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt index b0cdb22e..8249c8f6 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt @@ -2,7 +2,6 @@ package com.runnect.runnect.presentation.discover import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.runnect.runnect.data.dto.request.RequestPostCourseScrap import com.runnect.runnect.data.dto.response.ResponsePostScrap @@ -15,9 +14,7 @@ import com.runnect.runnect.domain.repository.CourseRepository import com.runnect.runnect.presentation.base.BaseViewModel import com.runnect.runnect.presentation.state.UiStateV2 import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import timber.log.Timber @@ -105,7 +102,6 @@ class DiscoverViewModel @Inject constructor( private fun getMarathonCourses() { launchWithHandler { courseRepository.getMarathonCourse() - .flowOn(Dispatchers.IO) .onStart { _marathonCourseGetState.value = UiStateV2.Loading }.collect { result -> From 3d477e9c4852e23243abfd8c979d7388f02a33fa Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 18:08:19 +0900 Subject: [PATCH 17/23] =?UTF-8?q?[refactor]=20ApiResult=20=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20kotlin.Result=EB=A1=9C=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ApiResultCall.kt => ResultCall.kt} | 31 +++++++++++-------- ...ultCallAdapter.kt => ResultCallAdapter.kt} | 9 +++--- ...Factory.kt => ResultCallAdapterFactory.kt} | 20 ++++++------ .../data/repository/CourseRepositoryImpl.kt | 2 +- .../runnect/data/service/CourseV2Service.kt | 4 +-- .../source/remote/RemoteCourseDataSource.kt | 3 +- .../com/runnect/runnect/di/RetrofitModule.kt | 4 +-- .../domain/repository/CourseRepository.kt | 3 +- 8 files changed, 39 insertions(+), 37 deletions(-) rename app/src/main/java/com/runnect/runnect/data/network/calladapter/{ApiResultCall.kt => ResultCall.kt} (65%) rename app/src/main/java/com/runnect/runnect/data/network/calladapter/{ApiResultCallAdapter.kt => ResultCallAdapter.kt} (55%) rename app/src/main/java/com/runnect/runnect/data/network/calladapter/{ApiResultCallAdapterFactory.kt => ResultCallAdapterFactory.kt} (52%) diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCall.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt similarity index 65% rename from app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCall.kt rename to app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt index c2ae341d..14409000 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCall.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt @@ -2,45 +2,50 @@ package com.runnect.runnect.data.network.calladapter import com.google.gson.Gson import com.runnect.runnect.data.dto.response.base.ErrorResponse -import com.runnect.runnect.data.network.ApiResult +import com.runnect.runnect.domain.common.RunnectException import retrofit2.Call import retrofit2.Callback import retrofit2.Response import okhttp3.Request import okio.Timeout -class ApiResultCall(private val call: Call) : Call> { +class ResultCall(private val call: Call) : Call> { private val gson = Gson() - override fun execute(): Response> { + override fun execute(): Response> { throw UnsupportedOperationException("ResultCall doesn't support execute") } - override fun enqueue(callback: Callback>) { + override fun enqueue(callback: Callback>) { call.enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { val apiResult = if (response.isSuccessful) { response.body()?.let { - ApiResult.Success(it) - } ?: ApiResult.Failure(code = response.code(), message = "Response body is null") + Result.success(it) + } ?: Result.failure( + RunnectException( + code = response.code(), + message = "Response body is null" + ) + ) } else { - parseErrorResponse(response) + Result.failure(parseErrorResponse(response)) } callback.onResponse( - this@ApiResultCall, + this@ResultCall, Response.success(apiResult) ) } override fun onFailure(call: Call, t: Throwable) { - callback.onFailure(this@ApiResultCall, t) + callback.onFailure(this@ResultCall, t) } }) } - private fun parseErrorResponse(response: Response<*>): ApiResult.Failure { + private fun parseErrorResponse(response: Response<*>): RunnectException { val errorJson = response.errorBody()?.string() return runCatching { @@ -49,19 +54,19 @@ class ApiResultCall(private val call: Call) : Call> { message ?: error ?: "알 수 없는 에러가 발생하였습니다." } - ApiResult.Failure( + RunnectException( code = errorBody.status, message = message ) }.getOrElse { - ApiResult.Failure( + RunnectException( code = response.code(), message = "알 수 없는 에러가 발생하였습니다." ) } } - override fun clone(): Call> = ApiResultCall(call.clone()) + override fun clone(): Call> = ResultCall(call.clone()) override fun isExecuted(): Boolean = call.isExecuted override fun cancel() = call.cancel() override fun isCanceled(): Boolean = call.isCanceled diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapter.kt similarity index 55% rename from app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapter.kt rename to app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapter.kt index 36a5614d..5c65e150 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapter.kt @@ -1,18 +1,17 @@ package com.runnect.runnect.data.network.calladapter -import com.runnect.runnect.data.network.ApiResult import retrofit2.Call import retrofit2.CallAdapter import java.lang.reflect.Type -class ApiResultCallAdapter( +class ResultCallAdapter( private val responseType: Type -) : CallAdapter>> { +) : CallAdapter>> { override fun responseType() = responseType // Retrofit의 Call을 Flow<>로 변환 - override fun adapt(call: Call): Call> { - return ApiResultCall(call) + override fun adapt(call: Call): Call> { + return ResultCall(call) } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapterFactory.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapterFactory.kt similarity index 52% rename from app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapterFactory.kt rename to app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapterFactory.kt index 683cc6c1..9caa0398 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ApiResultCallAdapterFactory.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapterFactory.kt @@ -1,35 +1,37 @@ package com.runnect.runnect.data.network.calladapter -import com.runnect.runnect.data.network.ApiResult import retrofit2.Call import retrofit2.CallAdapter import retrofit2.Retrofit import java.lang.reflect.ParameterizedType import java.lang.reflect.Type -class ApiResultCallAdapterFactory private constructor() : CallAdapter.Factory() { +class ResultCallAdapterFactory private constructor() : CallAdapter.Factory() { override fun get( returnType: Type, annotations: Array, retrofit: Retrofit ): CallAdapter<*, *>? { - // suspend로 선언시 Call로 감싸짐 - // 최상위 타입이 Call인지 체크 + // 최상위 타입이 Call인지 체크(suspend로 선언시 Call로 감싸짐) if (getRawType(returnType) != Call::class.java) { return null } - check(returnType is ParameterizedType) { "Call return type must be parameterized as Call or Call" } + check(returnType is ParameterizedType) { + "Call return type must be parameterized as Call or Call" + } val responseType = getParameterUpperBound(0, returnType) - if (getRawType(responseType) != ApiResult::class.java) { + if (getRawType(responseType) != Result::class.java) { return null } - check(responseType is ParameterizedType) { "ApiResult return type must be parameterized as ApiResult or ApiResult" } + check(responseType is ParameterizedType) { + "ApiResult return type must be parameterized as ApiResult or ApiResult" + } - return ApiResultCallAdapter( + return ResultCallAdapter( getParameterUpperBound( 0, responseType @@ -39,6 +41,6 @@ class ApiResultCallAdapterFactory private constructor() : CallAdapter.Factory() companion object { @JvmStatic - fun create() = ApiResultCallAdapterFactory() + fun create() = ResultCallAdapterFactory() } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt index 9bdb76de..c6fa6a41 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt @@ -12,8 +12,8 @@ import com.runnect.runnect.data.dto.response.ResponsePostMyHistory import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostDiscoverUpload import com.runnect.runnect.data.dto.response.ResponsePostScrap +import com.runnect.runnect.data.network.mapToFlowResult import com.runnect.runnect.data.source.remote.RemoteCourseDataSource -import com.runnect.runnect.domain.common.Result import com.runnect.runnect.domain.entity.DiscoverSearchCourse import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.* import com.runnect.runnect.domain.entity.DiscoverUploadCourse diff --git a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt index 509cf24f..75d7198b 100644 --- a/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt +++ b/app/src/main/java/com/runnect/runnect/data/service/CourseV2Service.kt @@ -1,11 +1,9 @@ package com.runnect.runnect.data.service -import com.runnect.runnect.data.network.ApiResult import com.runnect.runnect.data.dto.response.ResponseGetDiscoverMarathon -import kotlinx.coroutines.flow.Flow import retrofit2.http.GET interface CourseV2Service { @GET("/api/public-course/marathon") - suspend fun getMarathonCourse(): ApiResult + suspend fun getMarathonCourse(): Result } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt index 4b33888d..e4608886 100644 --- a/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt +++ b/app/src/main/java/com/runnect/runnect/data/source/remote/RemoteCourseDataSource.kt @@ -1,6 +1,5 @@ package com.runnect.runnect.data.source.remote -import com.runnect.runnect.data.network.ApiResult import com.runnect.runnect.data.dto.request.RequestPatchPublicCourse import com.runnect.runnect.data.dto.request.RequestPostCourseScrap import com.runnect.runnect.data.dto.request.RequestPostPublicCourse @@ -22,7 +21,7 @@ class RemoteCourseDataSource @Inject constructor( private var courseV2Service: CourseV2Service, private val courseService: CourseService ) { - suspend fun getMarathonCourse(): ApiResult = + suspend fun getMarathonCourse(): Result = courseV2Service.getMarathonCourse() suspend fun getRecommendCourse( diff --git a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt index a2837336..348e27c6 100644 --- a/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt +++ b/app/src/main/java/com/runnect/runnect/di/RetrofitModule.kt @@ -3,7 +3,7 @@ package com.runnect.runnect.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.runnect.runnect.BuildConfig import com.runnect.runnect.application.ApplicationClass -import com.runnect.runnect.data.network.calladapter.ApiResultCallAdapterFactory +import com.runnect.runnect.data.network.calladapter.ResultCallAdapterFactory import com.runnect.runnect.data.network.interceptor.ResponseInterceptor import com.runnect.runnect.data.service.* import com.runnect.runnect.data.repository.* @@ -125,7 +125,7 @@ object RetrofitModule { .baseUrl(baseUrl) .client(client) .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(ApiResultCallAdapterFactory.create()) + .addCallAdapterFactory(ResultCallAdapterFactory.create()) .build() return retrofit ?: throw RuntimeException("Retrofit creation failed.") diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt index 915fc967..ebaf16c0 100644 --- a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt +++ b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt @@ -11,7 +11,6 @@ import com.runnect.runnect.data.dto.response.ResponsePostMyDrawCourse import com.runnect.runnect.data.dto.response.ResponsePostMyHistory import com.runnect.runnect.data.dto.response.ResponsePostScrap import com.runnect.runnect.data.dto.response.ResponsePutMyDrawCourse -import com.runnect.runnect.domain.common.Result import com.runnect.runnect.domain.entity.CourseDetail import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.MarathonCourse import com.runnect.runnect.domain.entity.DiscoverSearchCourse @@ -24,7 +23,7 @@ import okhttp3.RequestBody import retrofit2.Response interface CourseRepository { - suspend fun getMarathonCourse(): Flow>> + suspend fun getMarathonCourse(): Flow>> suspend fun getRecommendCourse( pageNo: String, From 7945ef2e3cc8ad88a4565acaea8e2927b798340f Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 18:08:34 +0900 Subject: [PATCH 18/23] =?UTF-8?q?[del]=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/runnect/data/network/ApiResult.kt | 25 --------------- .../runnect/runnect/domain/common/Result.kt | 32 ------------------- 2 files changed, 57 deletions(-) delete mode 100644 app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt delete mode 100644 app/src/main/java/com/runnect/runnect/domain/common/Result.kt diff --git a/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt b/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt deleted file mode 100644 index f71b3a88..00000000 --- a/app/src/main/java/com/runnect/runnect/data/network/ApiResult.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.runnect.runnect.data.network - -import com.runnect.runnect.domain.common.Result -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -sealed class ApiResult { - data class Success(val body: R) : ApiResult() - data class Failure( - val code: Int, - val message: String?, - ) : ApiResult() - - // ApiResult 형태를 Flow> 형태로 매핑 - fun mapToFlowResult( - mapper: (R) -> D - ): Flow> = flow { - emit( - when (this@ApiResult) { - is Success -> Result.Success(mapper(body)) - is Failure -> Result.Failure(code, message) - } - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/domain/common/Result.kt b/app/src/main/java/com/runnect/runnect/domain/common/Result.kt deleted file mode 100644 index f2a98b24..00000000 --- a/app/src/main/java/com/runnect/runnect/domain/common/Result.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.runnect.runnect.domain.common - -sealed interface Result { - data class Success(val data: T) : Result - data class Failure( - val code: Int, - val message: String?, - ) : Result - - fun isSuccess(): Boolean = this is Success - fun isFailure(): Boolean = this is Failure - - fun getOrNull(): T? = when (this) { - is Success -> data - is Failure -> null - } - - fun exceptionOrNull(): Throwable? = when (this) { - is Success -> null - is Failure -> RunnectException(code, message) - } -} - -fun Result.onSuccess(action: (T) -> Unit): Result { - if (this is Result.Success) action(data) - return this -} - -fun Result.onFailure(action: (RunnectException) -> Unit): Result { - if (this is Result.Failure) action(RunnectException(code, message)) - return this -} \ No newline at end of file From a3f89135eaa0ab34a665948a0306f77d6f39fa8f Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 18:09:05 +0900 Subject: [PATCH 19/23] =?UTF-8?q?[feat]=20kotlin.Result=20->=20Flow=EB=A1=9C=20=EB=B3=80=ED=99=98=ED=95=98=EB=8A=94=20mapp?= =?UTF-8?q?er=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/data/network/mapToFlowResult.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt diff --git a/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt b/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt new file mode 100644 index 00000000..324cdb5c --- /dev/null +++ b/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt @@ -0,0 +1,22 @@ +package com.runnect.runnect.data.network + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +fun Result.mapToFlowResult( + mapper: (R) -> D +): Flow> = flow { + val result = when { + this@mapToFlowResult.isSuccess -> Result.success( + // CallAdapter에서 body가 null인 경우도 걸러주고 있으므로 + // Result.success의 데이터가 null인 경우는 없을듯함 + mapper(this@mapToFlowResult.getOrNull()!!) + ) + + else -> Result.failure( + this@mapToFlowResult.exceptionOrNull()!! + ) + } + + emit(result) +} \ No newline at end of file From 2aa6be77945ab8d16b3019839bb5a2664beafdc2 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 18:09:58 +0900 Subject: [PATCH 20/23] =?UTF-8?q?[feat]=20Exception=EC=9D=B4=20null?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EA=B8=B0=EB=B3=B8=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=8D=98=EC=A7=80=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/runnect/runnect/data/network/mapToFlowResult.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt b/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt index 324cdb5c..958d453f 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/mapToFlowResult.kt @@ -1,5 +1,6 @@ package com.runnect.runnect.data.network +import com.runnect.runnect.domain.common.RunnectException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -14,7 +15,7 @@ fun Result.mapToFlowResult( ) else -> Result.failure( - this@mapToFlowResult.exceptionOrNull()!! + this@mapToFlowResult.exceptionOrNull() ?: RunnectException() ) } From f4fe66df36de82913fb28cd726b6f6b629b1329b Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 18:10:22 +0900 Subject: [PATCH 21/23] =?UTF-8?q?[feat]=20Exception=20toLog=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/domain/common/RunnectException.kt | 15 ++++++++++++--- .../presentation/discover/DiscoverViewModel.kt | 3 +-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/domain/common/RunnectException.kt b/app/src/main/java/com/runnect/runnect/domain/common/RunnectException.kt index 81823624..aadbdce7 100644 --- a/app/src/main/java/com/runnect/runnect/domain/common/RunnectException.kt +++ b/app/src/main/java/com/runnect/runnect/domain/common/RunnectException.kt @@ -1,9 +1,18 @@ package com.runnect.runnect.domain.common class RunnectException( - val code: Int, - override val message: String? + val code: Int = -1, + override val message: String? = "알 수 없는 에러가 발생하였습니다.", + override val cause: Throwable? = null ) : Throwable(message) { - fun toLog() = "$message(${code})" + fun toLog() = "$message (${code})" +} + +fun Throwable.toLog(): String { + return if(this is RunnectException) { + this.toLog() + } else { + "$message (unknown)" + } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt index 8249c8f6..36f2f80e 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/DiscoverViewModel.kt @@ -5,8 +5,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.runnect.runnect.data.dto.request.RequestPostCourseScrap import com.runnect.runnect.data.dto.response.ResponsePostScrap -import com.runnect.runnect.domain.common.onFailure -import com.runnect.runnect.domain.common.onSuccess +import com.runnect.runnect.domain.common.toLog import com.runnect.runnect.domain.entity.DiscoverMultiViewItem.* import com.runnect.runnect.domain.entity.DiscoverBanner import com.runnect.runnect.domain.repository.BannerRepository From 6df7c7364642914185e910a60e7f63f62b987865 Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 18:21:36 +0900 Subject: [PATCH 22/23] =?UTF-8?q?[refactor]=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runnect/data/network/calladapter/ResultCall.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt index 14409000..556da262 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt @@ -26,7 +26,7 @@ class ResultCall(private val call: Call) : Call> { } ?: Result.failure( RunnectException( code = response.code(), - message = "Response body is null" + message = ERROR_MSG_RESPONSE_IS_NULL ) ) } else { @@ -51,7 +51,7 @@ class ResultCall(private val call: Call) : Call> { return runCatching { val errorBody = gson.fromJson(errorJson, ErrorResponse::class.java) val message = errorBody?.run { - message ?: error ?: "알 수 없는 에러가 발생하였습니다." + message ?: error ?: ERROR_MSG_COMMON } RunnectException( @@ -61,7 +61,7 @@ class ResultCall(private val call: Call) : Call> { }.getOrElse { RunnectException( code = response.code(), - message = "알 수 없는 에러가 발생하였습니다." + message = ERROR_MSG_COMMON ) } } @@ -72,4 +72,9 @@ class ResultCall(private val call: Call) : Call> { override fun isCanceled(): Boolean = call.isCanceled override fun request(): Request = call.request() override fun timeout(): Timeout = call.timeout() + + companion object { + private const val ERROR_MSG_COMMON = "알 수 없는 에러가 발생하였습니다." + private const val ERROR_MSG_RESPONSE_IS_NULL = "데이터를 불러올 수 없습니다." + } } \ No newline at end of file From de702f1b06552b4338700aac04bcc1bc336a6c4e Mon Sep 17 00:00:00 2001 From: Donghyeon0915 Date: Wed, 27 Mar 2024 19:52:10 +0900 Subject: [PATCH 23/23] =?UTF-8?q?[refactor]=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/network/calladapter/ResultCallAdapter.kt | 2 +- .../data/repository/CourseRepositoryImpl.kt | 12 ++++++------ .../runnect/domain/repository/CourseRepository.kt | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapter.kt b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapter.kt index 5c65e150..fba1d638 100644 --- a/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapter.kt +++ b/app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCallAdapter.kt @@ -10,7 +10,7 @@ class ResultCallAdapter( override fun responseType() = responseType - // Retrofit의 Call을 Flow<>로 변환 + // Retrofit의 Call을 Result<>로 변환 override fun adapt(call: Call): Call> { return ResultCall(call) } diff --git a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt index c6fa6a41..e166327e 100644 --- a/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt +++ b/app/src/main/java/com/runnect/runnect/data/repository/CourseRepositoryImpl.kt @@ -38,7 +38,7 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun getRecommendCourse( pageNo: String, sort: String - ): kotlin.Result = runCatching { + ): Result = runCatching { val response = remoteCourseDataSource.getRecommendCourse( pageNo = pageNo, sort = sort @@ -49,12 +49,12 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc } } - override suspend fun getCourseSearch(keyword: String): kotlin.Result?> = + override suspend fun getCourseSearch(keyword: String): Result?> = runCatching { remoteCourseDataSource.getCourseSearch(keyword = keyword).data?.toDiscoverSearchCourses() } - override suspend fun getMyCourseLoad(): kotlin.Result?> = + override suspend fun getMyCourseLoad(): Result?> = runCatching { remoteCourseDataSource.getMyCourseLoad().data?.toUploadCourses() } @@ -87,14 +87,14 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun getCourseDetail( publicCourseId: Int - ): kotlin.Result = runCatching { + ): Result = runCatching { remoteCourseDataSource.getCourseDetail(publicCourseId = publicCourseId).data?.toCourseDetail() } override suspend fun patchPublicCourse( publicCourseId: Int, requestPatchPublicCourse: RequestPatchPublicCourse - ): kotlin.Result = runCatching { + ): Result = runCatching { remoteCourseDataSource.patchPublicCourse( publicCourseId = publicCourseId, requestPatchPublicCourse = requestPatchPublicCourse @@ -103,7 +103,7 @@ class CourseRepositoryImpl @Inject constructor(private val remoteCourseDataSourc override suspend fun postCourseScrap( requestPostCourseScrap: RequestPostCourseScrap - ): kotlin.Result = runCatching { + ): Result = runCatching { remoteCourseDataSource.postCourseScrap(requestPostCourseScrap = requestPostCourseScrap).data } } \ No newline at end of file diff --git a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt index ebaf16c0..fd89760f 100644 --- a/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt +++ b/app/src/main/java/com/runnect/runnect/domain/repository/CourseRepository.kt @@ -23,16 +23,16 @@ import okhttp3.RequestBody import retrofit2.Response interface CourseRepository { - suspend fun getMarathonCourse(): Flow>> + suspend fun getMarathonCourse(): Flow>> suspend fun getRecommendCourse( pageNo: String, sort: String - ): kotlin.Result + ): Result - suspend fun getCourseSearch(keyword: String): kotlin.Result?> + suspend fun getCourseSearch(keyword: String): Result?> - suspend fun getMyCourseLoad(): kotlin.Result?> + suspend fun getMyCourseLoad(): Result?> suspend fun postUploadMyCourse(requestPostPublicCourse: RequestPostPublicCourse): ResponsePostDiscoverUpload @@ -46,12 +46,12 @@ interface CourseRepository { image: MultipartBody.Part, data: RequestBody ): Response - suspend fun getCourseDetail(publicCourseId: Int): kotlin.Result + suspend fun getCourseDetail(publicCourseId: Int): Result suspend fun patchPublicCourse( publicCourseId: Int, requestPatchPublicCourse: RequestPatchPublicCourse - ): kotlin.Result + ): Result - suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): kotlin.Result + suspend fun postCourseScrap(requestPostCourseScrap: RequestPostCourseScrap): Result } \ No newline at end of file