Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: CallAdapter 적용 및 검증 테스트 작성 #558 #574

Merged
merged 96 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
4e11110
refactor: ResponseResult interface와 응답 class 분리
hxeyexn Nov 28, 2024
c992a87
refactor: Exception message 기본 인자 설정
hxeyexn Nov 28, 2024
d17777f
refactor: 204 응답 처리 로직 제거
hxeyexn Nov 28, 2024
3f60c5b
feat: NetworkResultCall 구현
hxeyexn Nov 28, 2024
eb2ef9f
refactor: status code, exception 메시지 상수화
hxeyexn Nov 28, 2024
ffe84f1
feat: NetworkResultCallAdapter 구현
hxeyexn Nov 28, 2024
2ce3a7d
feat: CallAdapterFactory 구현
hxeyexn Nov 28, 2024
431975d
feat: Retrofit 초기화 시 CallAdapterFactory 추가
hxeyexn Nov 28, 2024
b058b24
refactor: 타임라인 기능 CallAdapter 적용
hxeyexn Nov 28, 2024
d2e5e9d
refactor: 카테고리(전 추억) 기능 CallAdapter 적용
hxeyexn Nov 28, 2024
84b464f
refactor: 스타카토 기능 CallAdapter 적용
hxeyexn Nov 28, 2024
535a3e9
refactor: 댓글 기능 CallAdapter 적용
hxeyexn Nov 28, 2024
b1e90fa
refactor: 이미지 업로드 기능 CallAdapter 적용
hxeyexn Nov 28, 2024
b334d4a
refactor: 마이페이지 기능 CallAdapter 적용
hxeyexn Nov 28, 2024
5c815fc
refactor: 로그인 기능 CallAdapter 적용
hxeyexn Nov 28, 2024
fc26942
refactor: 멤버 기능 CallAdapter 적용
hxeyexn Nov 28, 2024
7501253
refactor: 이전 handleApiResponse 제거
hxeyexn Nov 28, 2024
3b534c2
refactor: handleApiResponse2 이름 변경
hxeyexn Nov 28, 2024
9b9ad03
refactor: ResponseResult -> ApiResult로 이름 변경
hxeyexn Nov 28, 2024
f846206
refactor: ApiResponseHandler 이름 변경
hxeyexn Nov 28, 2024
b32ecc8
refactor: NetworkResultCall 이름 변경
hxeyexn Nov 28, 2024
be27563
refactor: NetworkResultCallAdapter 이름 변경
hxeyexn Nov 28, 2024
7419d07
refactor: NetworkResultCallAdapterFactory 이름 변경
hxeyexn Nov 28, 2024
8fcf08d
refactor: ApiResult 처리 로직 추가
hxeyexn Dec 1, 2024
a9b13eb
refactor: 카테고리 리포지터리 ApiResult 처리 확장 함수 적용
hxeyexn Dec 1, 2024
63a9d7f
refactor: 스타카토 리포지터리 ApiResult 처리 확장 함수 적용
hxeyexn Dec 7, 2024
d01b05c
refactor: 타임라인 리포지터리 ApiResult 처리 확장 함수 적용
hxeyexn Dec 7, 2024
d5c359c
refactor: 마이페이지 리포지터리 ApiResult 처리 확장 함수 적용
hxeyexn Dec 7, 2024
334d563
refactor: 댓글 리포지터리 ApiResult 처리 확장 함수 적용
hxeyexn Dec 7, 2024
f1605ee
refactor: 멤버 리포지터리 ApiResult 처리 확장 함수 적용
hxeyexn Dec 7, 2024
ca5b48e
refactor: 로그인 리포지터리 ApiResult 처리 확장 함수 적용
hxeyexn Dec 7, 2024
76d4517
refactor: 이미지 리포지터리 ApiResult 처리 확장 함수 적용
hxeyexn Dec 7, 2024
6e7253b
build: coroutines test 의존성 추가
hxeyexn Dec 17, 2024
a59d4d8
build: mockwebserver 의존성 추가
hxeyexn Dec 17, 2024
06ebf59
build: JUnit5 의존성 추가
hxeyexn Dec 17, 2024
202f50c
feat: CoroutinesTestExtension 추가
hxeyexn Dec 17, 2024
ea6d815
test: api 요청 성공(200) 시 CallAdapter 동작 테스트 추가
hxeyexn Dec 18, 2024
037b169
test: api 요청 실패(400) 시 CallAdapter 동작 테스트 추가
hxeyexn Dec 18, 2024
1ecc3d8
test: MockResponse 생성 로직 함수로 분리
hxeyexn Dec 18, 2024
7f398af
test: api 요청 실패(413) 시 CallAdapter 동작 테스트 추가
hxeyexn Dec 18, 2024
bead6ca
test: api 요청 실패(예외 발생) 시 CallAdapter 동작 테스트 추가
hxeyexn Dec 18, 2024
0ff8359
test: api 요청 실패(500) 시 CallAdapter 동작 테스트 추가
hxeyexn Dec 18, 2024
ae28100
test: api 요청 실패(403) 시 CallAdapter 동작 테스트 추가
hxeyexn Dec 18, 2024
2e021d6
test: api 요청 실패(401) 시 CallAdapter 동작 테스트 추가
hxeyexn Dec 18, 2024
908db7e
test: api 요청 성공(200) CallAdapter 테스트 상태 코드 201로 수정
hxeyexn Dec 18, 2024
10f23b6
test: api 요청 성공(200) 시 CallAdapter 테스트 추가
hxeyexn Dec 18, 2024
3f41b05
test: api 응답 TestFixture 추가
hxeyexn Dec 18, 2024
8e1e687
refactor: CallAdapterTest 이름 변경
hxeyexn Dec 18, 2024
7768209
refactor: request test fixture 변수를 함수로 변경
hxeyexn Dec 18, 2024
ce8cfe5
refactor: makeFakeImageFile() 함수 이름 변경
hxeyexn Dec 18, 2024
2ece854
refactor: MockWebServerFixture 이름 변경
hxeyexn Dec 18, 2024
e26952f
refactor: makeMockResponse() 함수 이름 변경
hxeyexn Dec 18, 2024
3b8ff3d
style: ktlintformat
hxeyexn Dec 18, 2024
178357c
refactor: 변수 타입 명시
hxeyexn Dec 27, 2024
7ebc13b
build: testLogging 추가
hxeyexn Dec 27, 2024
f9b5dfd
build: testLogging event 추가
hxeyexn Dec 27, 2024
f49d9f9
build: testLogging exceptionFormat 설정
hxeyexn Dec 28, 2024
05cface
test: setUp()에서 MockWebServer 초기화
hxeyexn Dec 29, 2024
35e890e
test: assertTrue -> assertInstanceOf로 변경
hxeyexn Dec 29, 2024
805b7fa
fix: ci 수정을 위한 response 출력문 추가
hxeyexn Dec 29, 2024
c8dc262
fix: ci 오류 해결을 위한 errorBody 출력문 추가
hxeyexn Dec 29, 2024
31e9c9f
test: StaccatoClient 초기화
hxeyexn Dec 29, 2024
e841063
refactor: retrofit 초기화 방식 변경
hxeyexn Dec 30, 2024
eeaf574
refactor: CI 오류 해결을 위한 print문 제거
hxeyexn Dec 30, 2024
9e1b3cf
build: AssertJ 의존성 추가
hxeyexn Dec 30, 2024
14a7886
test: AssertJ를 활용하여 검증 코드 수정
hxeyexn Dec 30, 2024
f1d0c31
fix: conflict 해결
hxeyexn Jan 11, 2025
07eebb6
build: junit vintage engine 의존성 추가
hxeyexn Jan 11, 2025
8c99e4b
refactor: FakeFileProvider, ApiDataFixture 폴더 이동
hxeyexn Jan 11, 2025
3dca2c4
Merge branch 'develop' into feature/#558-apply-calladapter
hxeyexn Jan 11, 2025
2d610db
Merge branch 'develop' into feature/#558-apply-calladapter
hxeyexn Jan 12, 2025
b2b759f
Merge branch 'develop' into feature/#558-apply-calladapter
hxeyexn Jan 16, 2025
9361ce8
style: 불필요한 import 제거
hxeyexn Jan 16, 2025
d3bfb04
style: trailing comma 제거
hxeyexn Jan 16, 2025
102d843
test: api 요청 실패(413) 시 CallAdapter 동작 테스트 명 변경
hxeyexn Jan 16, 2025
36ec3e0
refactor: callType 변수를 responseType 으로 이름 변경
hxeyexn Jan 16, 2025
936f8b7
refactor: proxy 변수를 delegate 로 이름 변경
hxeyexn Jan 16, 2025
35ba213
refactor: ApiResult<T> 처리 함수 표현식으로 변경
hxeyexn Jan 16, 2025
5eec9a6
test: 조회 성공(200) 테스트 FakeApiService 적용
hxeyexn Jan 16, 2025
762a010
test: 생성 성공/실패/예외 테스트 FakeApiService 적용
hxeyexn Jan 16, 2025
63b2b33
test: 인증 정보 불일치(403) 테스트 FakeApiService 적용
hxeyexn Jan 16, 2025
5e96090
test: CallAdapterTest 내 사용하지 않는 ApiService 제거
hxeyexn Jan 16, 2025
e869b50
test: 테스트명 오타 수정
hxeyexn Jan 16, 2025
b533341
test: dto 패키지 추가
hxeyexn Jan 16, 2025
c414f4e
test: Inline 학습 테스트 추가
hxeyexn Jan 16, 2025
12452ae
style: 코드 formatting
hxeyexn Jan 16, 2025
1d5e3e7
test: Inline 학습 테스트 assertAll 주석 처리
hxeyexn Jan 16, 2025
7650d8c
Merge branch 'develop' into feature/#558-apply-calladapter
hxeyexn Jan 18, 2025
71fee22
refactor: 예외 세분화 및 ApiResult<T>.onException이 예외 상태를 포획 하도록 수정
hxeyexn Jan 19, 2025
b39c759
Merge branch 'develop' into feature/#558-apply-calladapter
hxeyexn Jan 19, 2025
51954c3
refactor: LoginRepository에서 사용자 토큰을 저장하도록 변경
hxeyexn Jan 20, 2025
a554e09
refactor: ApiResult<T> 확장함수 inline 키워드 추가
hxeyexn Jan 20, 2025
5db7be4
refactor: ApiResult<T> 성공, 서버에러, 예외 별 처리 확장함수 파라미터명 변경
hxeyexn Jan 20, 2025
8a26c95
refactor: MemberRepository에서 사용자 토큰을 저장하도록 변경
hxeyexn Jan 20, 2025
084f788
refactor: ApiResult<Unit> 함수 추가 및 적용
hxeyexn Jan 20, 2025
372aab1
refactor: ApiResult<T>.onServerError 확장함수 status 파라미터 제거
hxeyexn Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions android/Staccato_AN/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import java.io.FileInputStream
import java.util.Properties

Expand All @@ -20,6 +21,7 @@ plugins {
alias(libs.plugins.firebaseCrashlytics)
alias(libs.plugins.mapsplatformSecretsGradlePlugin)
alias(libs.plugins.hiltAndroid)
alias(libs.plugins.androidJunit5)
}

android {
Expand All @@ -34,6 +36,8 @@ android {
versionName = "1.2.1"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["runnerBuilder"] =
"de.mannodermaus.junit5.AndroidJUnit5Builder"

buildConfigField("String", "TOKEN", "${localProperties["token"]}")
}
Expand Down Expand Up @@ -102,6 +106,13 @@ dependencies {
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

// JUnit5
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.vintage.engine)

// AssertJ
testImplementation(libs.assertj.core)

// Glide
implementation(libs.glide)

Expand All @@ -118,6 +129,7 @@ dependencies {

// OkHttp
implementation(libs.okhttp.logging.interceptor)
testImplementation(libs.okhttp.mockwebserver)

// Lifecycle
implementation(libs.lifecycle.viewmodel)
Expand Down Expand Up @@ -188,3 +200,10 @@ secrets {
ignoreList.add("keyToIgnore")
ignoreList.add("sdk.*")
}

tasks.withType<Test> {
testLogging {
events("started", "passed", "skipped", "failed", "standardError", "standardOut")
exceptionFormat = TestExceptionFormat.FULL
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ package com.on.staccato
import android.app.Application
import com.google.android.libraries.places.api.net.PlacesClient
import com.on.staccato.data.PlacesClientProvider
import com.on.staccato.data.StaccatoClient
import com.on.staccato.data.UserInfoPreferencesManager
import dagger.hilt.android.HiltAndroidApp
import retrofit2.Retrofit

@HiltAndroidApp
class StaccatoApplication : Application() {
override fun onCreate() {
super.onCreate()
// AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
retrofit = StaccatoClient.initialize()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retrofit 초기화 위치가 StaccatoClient에서 StaccatoApplication의 동반 객체로 변경된 이유가 궁금합니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI 오류 발생 원인과 관련 있습니다!

CI가 오류가 발생했던 시점을 돌이켜 보면, 테스트에서는 MockWebServer와 연결한 Retrofit을 참조하고 서버에서 들어오는 오류 응답을 변환하는 부분에서는 실제 서버와 연결된 레트로핏을 참조하고 있었습니다. 이로 인해 StacccatoClient을 초기화할 수 없는 문제가 발생했었습니다.

즉, 기존의 코드는 StaccatoClient 내부에서 Retrofit을 초기화하고 있었기 때문에 환경 별로 다른 Retrofit을 사용할 수 없는 구조였습니다. 이 문제를 해결하기 위해 StaccatoApplication의 동반 객체에서 retrofit을 지연 초기화하도록 변경했습니다.

userInfoPrefsManager = UserInfoPreferencesManager(applicationContext)
placesClient = PlacesClientProvider.getClient(this)
}

companion object {
lateinit var retrofit: Retrofit
lateinit var userInfoPrefsManager: UserInfoPreferencesManager
lateinit var placesClient: PlacesClient
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.on.staccato.data

import com.on.staccato.data.dto.Status

sealed interface ApiResult<T : Any>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sealed interface ResponseResult<T : Any> {
    class Success<T : Any>(val data: T) : ResponseResult<T>

    class ServerError<T : Any>(val status: Status, val message: String) : ResponseResult<T>

    class Exception<T : Any>(val e: Throwable, val message: String) : ResponseResult<T>
}

기존에는 위처럼 ResponseResult의 중괄호 안에 포함되는 구조였는데, 모든 클래스가 최상위 레벨에 정의되면서 직접 접근이 가능해졌네요!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sealed interfacce 의 장점을 잘 살린 것 같습니다!
코틀린 최고야! 해나 최고야!


class Success<T : Any>(val data: T) : ApiResult<T>

class ServerError<T : Any>(val status: Status, val message: String) : ApiResult<T>

sealed class Exception<T : Any> : ApiResult<T> {
class NetworkError<T : Any> : Exception<T>()

class UnknownError<T : Any> : Exception<T>()
}

inline fun <T : Any, R : Any> ApiResult<T>.handle(convert: (T) -> R): ApiResult<R> =
when (this) {
is Exception.NetworkError -> Exception.NetworkError()
is Exception.UnknownError -> Exception.UnknownError()
is ServerError -> ServerError(status, message)
is Success -> Success(convert(data))
}

fun ApiResult<Unit>.handle(): ApiResult<Unit> =
when (this) {
is Exception.NetworkError -> Exception.NetworkError()
is Exception.UnknownError -> Exception.UnknownError()
is ServerError -> ServerError(status, message)
is Success -> Success(data)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.on.staccato.data

import com.on.staccato.StaccatoApplication.Companion.retrofit
import com.on.staccato.data.StaccatoClient.getErrorResponse
import com.on.staccato.data.dto.ErrorResponse
import com.on.staccato.data.dto.Status
import okhttp3.Request
import okhttp3.ResponseBody
import okio.Timeout
import retrofit2.Call
import retrofit2.HttpException
import retrofit2.Response
import java.io.IOException

class ApiResultCall<T : Any>(
private val delegate: Call<T>,
) : Call<ApiResult<T>> {
override fun enqueue(callback: retrofit2.Callback<ApiResult<T>>) {
delegate.enqueue(
object : retrofit2.Callback<T> {
override fun onResponse(
call: Call<T>,
response: Response<T>,
) {
val networkResult: ApiResult<T> = handleApiResponse { response }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DataSource의 메서드마다 적용해주던 handleApiResponse를 ApiResultCall 내부로 이동시켜 중복 코드를 없앴네요! 속시원~

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

속시원~~ 😆

callback.onResponse(this@ApiResultCall, Response.success(networkResult))
}

override fun onFailure(
call: Call<T>,
throwable: Throwable,
) {
val exception = handleException<T>(throwable)
callback.onResponse(this@ApiResultCall, Response.success(exception))
}
},
)
}

override fun execute(): Response<ApiResult<T>> = throw NotImplementedError()

override fun clone(): Call<ApiResult<T>> = ApiResultCall(delegate.clone())

override fun isExecuted(): Boolean = delegate.isExecuted

override fun cancel() {
delegate.cancel()
}

override fun isCanceled(): Boolean = delegate.isCanceled

override fun request(): Request = delegate.request()

override fun timeout(): Timeout = delegate.timeout()
}

private const val CREATED = 201
private const val NOT_FOUND_ERROR_BODY = "errorBody를 찾을 수 없습니다."

private fun <T : Any> handleApiResponse(execute: () -> Response<T>): ApiResult<T> {
Copy link
Contributor

@Junyoung-WON Junyoung-WON Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CallAdapter가 직접 네트워크 호출의 대한 응답을 Call로 변환해주는 줄 알았는데, Call로 변환해주는 역할은 ApiResultCall에게 위임되었군요! 😲
결국 ApiResultCall의 역할은 Call를 Call<ApiResult>로 바꿔주는 것이네요~ 👍

한가지 궁금증이 생겼습니다!
Response의 결과에 따라 ApiResponse의 제네릭 타입을 지정하여 반환해주는 handleApiResponse 메서드는 ApiResultCall이 사용하기 때문에 ApiResultCall의 책임이라고 생각했어요. 그래서 해당 메서드가 ApiResultCall의 멤버 메서드로 들어가도 좋겠다는 생각이 들었습니다.
그렇다면 handleApiResponse 메서드를 ApiResultCall 클래스의 멤버로 두지 않은 이유가 따로 있으신건가요? 해나의 구현 의도가 궁금합니다~! 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

응답에 따라 결과를 처리하는 부분은 Call의 역할이 아니라고 생각해서 handleApiResponse를 Call 외부에서 가지고 있도록 구현했습니다!

return try {
val response: Response<T> = execute()
val body: T? = response.body()

when {
response.isSuccessful && response.code() == CREATED -> Success(body as T)
response.isSuccessful && body != null -> Success(body)
else -> {
val errorBody: ResponseBody =
response.errorBody()
?: throw IllegalArgumentException(NOT_FOUND_ERROR_BODY)
val errorResponse: ErrorResponse = retrofit.getErrorResponse(errorBody)
ServerError(
status = Status.Message(errorResponse.status),
message = errorResponse.message,
)
}
}
} catch (httpException: HttpException) {
ServerError(status = Status.Code(httpException.code()), message = httpException.message())
} catch (throwable: Throwable) {
handleException<T>(throwable)
}
}

private fun <T : Any> handleException(throwable: Throwable) =
when (throwable) {
is IOException -> Exception.NetworkError<T>()
else -> Exception.UnknownError<T>()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.on.staccato.data

import retrofit2.Call
import retrofit2.CallAdapter
import java.lang.reflect.Type

class ApiResultCallAdapter(
private val resultType: Type,
) : CallAdapter<Type, Call<ApiResult<Type>>> {
override fun responseType(): Type = resultType

override fun adapt(call: Call<Type>): Call<ApiResult<Type>> = ApiResultCall(call)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.on.staccato.data

import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

class ApiResultCallAdapterFactory : CallAdapter.Factory() {
override fun get(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그를 찍어보니 get() 메서드는 returnType이 같은 경우 앱 실행 동안 한 번만 호출되는 것 같아요! 내부적으로 CallAdapter를 캐싱하나? 신기하네요...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요! Retrofit 내부 구현을 살펴보면 callAdapter()nextCallAdapter() 메서드를 활용해 사용 가능한 팩토리 목록 중 returnType이 일치하는 CallAdapter를 찾아 반환하는 구조로 되어 있어요! 😄

이러한 구조라서 동일한 returnType에 대해서는 get() 메서드를 중복 호출하지 않는 것 같아요~

image

returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit,
): CallAdapter<*, *>? {
if (getRawType(returnType) != Call::class.java) {
return null
}

val responseType = getParameterUpperBound(0, returnType as ParameterizedType)

if (getRawType(responseType) != ApiResult::class.java) {
return null
}

val resultType = getParameterUpperBound(0, responseType as ParameterizedType)
return ApiResultCallAdapter(resultType)
}

companion object {
fun create(): ApiResultCallAdapterFactory = ApiResultCallAdapterFactory()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

companion object 안에 팩토리 메서드 create()를 구현하신 이유가 있을까요?
같은 맥락에서, StaccatoClient.kt의 addCallAdapterFactory에서 생성자가 아닌 create()를 호출하신 이유도 궁금합니다!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드의 가독성을 위해서일까요...? 🤔 create() 메서드가 명시적으로 "생성한다"라는 의미를 나타내니까요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ApiResultCallAdapterFactory의 생성 방식을 StaccatoClient가 구체적으로 알 필요가 없다고 생각했습니다.
즉, 추후 팩토리의 생성 로직이 변경 되더라도 외부에서는 create()만 호출하면 되기 때문에 수정 범위가 줄어듭니다.

이러한 이유로 Retrofit2의 ScalarsConvertFactory와 OptionalConverterFactory, KotlinSerializationConverterFactory 등도 create()를 활용해 Factory를 생성하고 있습니다!

ScalarsConverterFactory.java

image

OptionalConverterFactory.java

image

KotlinSerializationConverterFactory

image

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.on.staccato.data

import com.on.staccato.presentation.util.ExceptionState

inline fun <T : Any> ApiResult<T>.onSuccess(action: (T) -> Unit): ApiResult<T> =
apply {
if (this is Success<T>) action(data)
}

inline fun <T : Any> ApiResult<T>.onServerError(action: (message: String) -> Unit): ApiResult<T> =
apply {
if (this is ServerError<T>) action(message)
}

inline fun <T : Any> ApiResult<T>.onException(action: (exceptionState: ExceptionState) -> Unit): ApiResult<T> =
apply {
if (this is Exception<T>) {
when (this) {
is Exception.NetworkError -> action(ExceptionState.NetworkError)
is Exception.UnknownError -> action(ExceptionState.UnknownError)
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,19 @@ object StaccatoClient {

private val jsonBuilder = Json { coerceInputValues = true }

private val provideRetrofit =
fun initialize(): Retrofit =
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(provideHttpClient)
.addConverterFactory(
jsonBuilder.asConverterFactory("application/json".toMediaType()),
)
.addCallAdapterFactory(ApiResultCallAdapterFactory.create())
.build()

fun getErrorResponse(errorBody: ResponseBody): ErrorResponse {
return provideRetrofit.responseBodyConverter<ErrorResponse>(
fun Retrofit.getErrorResponse(errorBody: ResponseBody): ErrorResponse =
responseBodyConverter<ErrorResponse>(
Comment on lines +40 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말..... 전혀 예상치 못한 곳에서 CI 에러가 나타난 원인이 여기에 있었다뇨... 😂
CI 로그를 뜯어보며 겨우 찾아내었네요! 정말 고생하셨습니다 👏👏👏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫠🫠🫠🫠🫠

ErrorResponse::class.java,
ErrorResponse::class.java.annotations,
).convert(errorBody) ?: throw IllegalArgumentException("errorBody를 변환할 수 없습니다.")
}

fun <T> create(service: Class<T>): T {
return provideRetrofit.create(service)
}
}
Loading
Loading