728x90
Retrofit2을 이용해 여러 네트워크 통신 방법을 구현한 코드들을 정리했습니다.
Retrofit2 인터페이스
@GET(API.SEARCH_WEATHER)
fun search_weather2(
@Query("q") location : String,
@Query("lang") lang : String) : Single<JsonElement>
@GET(API.SEARCH_WEATHER)
suspend fun search_weather(
@Query("q") location : String,
@Query("lang") lang : String) : ApiResponse<JsonElement>
@GET(API.SEARCH_WEATHER)
fun search_weather3(
@Query("q") location : String,
@Query("lang") lang : String) : Call<JsonElement>
OkHttp Interceptor를 작성
class RetrofitClient_Weather : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
Log.d(
Constants.TAG, "${this@RetrofitClient_Weather::class.java.simpleName} " +
"intercept() / chain : $chain")
/**
* 1) 공통 파라미터 장착
*/
val originalRequest = chain.request()
val addClientIdUrl = originalRequest .url.newBuilder()
.addQueryParameter("key", API.API_ID_WEATHER)
.addQueryParameter("days", "3")
.build()
Log.d(
Constants.TAG, "${this@RetrofitClient_Weather::class.java.simpleName} " +
"intercept() / addClicentUrl : $addClientIdUrl")
val finalRequest = originalRequest.newBuilder()
.url(addClientIdUrl)
.method(originalRequest.method, originalRequest.body)
.build()
Log.d(
Constants.TAG, "${this@RetrofitClient_Weather::class.java.simpleName} " +
"intercept() / finalRequest : ${finalRequest}")
/**
* 2) 서버로 부터 응답받기
*/
val response = chain.proceed(finalRequest) //오리지날 요청으로 변경하면 401에러 문제없이 출력됨
Log.d(
Constants.TAG, "${this@RetrofitClient_Weather::class.java.simpleName} " +
"intercept() / responseCode : ${response.code}")
return response
}
}
OkHttp Client에 Interceptor를 추가하고, 이를 이용해 Retrofit2 Client 생성하는 코드
fun retrofitModule(wthrbaseUrl : String) = module {
single<OkHttpClient> {
OkHttpClient.Builder().apply {
addInterceptor(get<RetrofitClient_Weather>())
connectTimeout(3000, TimeUnit.MILLISECONDS) //커넥션 작업의 타임아웃
readTimeout(3000, TimeUnit.MILLISECONDS) //읽기 작업의 타임아웃
writeTimeout(3000, TimeUnit.MILLISECONDS) //쓰기 작업의 타임아웃
retryOnConnectionFailure(false) //실패할 경우 다시 시도할 것인가?
}.build()
}
single<Retrofit_Service> {
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
//Retrofit 반환타입이 AirResponse<T>인 경우
.addCallAdapterFactory(CoroutinesResponseCallAdapterFactory())
//RxJava를 이용하는 경우
addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(wthrbaseUrl)
// 위에서 설정한 client로 retrofit client를 설정한다.
.client(get<OkHttpClient>())
.build()
.create(Retrofit_Service::class.java)
}
}
호출 코드 RxJava
val disposable = weatherRepository.search_weather3(locationFormat, lang)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.filter { true }
.map { it.asJsonObject }
.subscribeBy(
onError = { e ->
println(e.toString())
},
onSuccess = { response ->
Log.d(Constants.TAG, "${this::class.simpleName} " +
"search()-- 출력")
parsingWeatherJsonData(response).apply {
loadingLiveData.value = false
}
}
)
Thread{
try {
Thread.sleep(3000)
Log.d(Constants.TAG, "${this::class.simpleName} " +
"search()-- 진행 중")
}catch (e: Exception){
e.printStackTrace()
}
Log.d(Constants.TAG, "${this::class.simpleName} " +
"search()-- 종료")
disposable.dispose()
}.start()
호출 코드 Sandwich (enqueue() 대신 request를 이용해 같은 기능을 구현할 수 있는 것으로 판단됨)
- Interface의 반환타입이 Call<타입>일때 사용할 수 있습니다.
weatherRepository.search_weather(locationFormat, lang).request { response ->
when (response) {
// handles the success case when the API request gets a successful response.
is ApiResponse.Success -> {
}
// handles error cases when the API request gets an error response.
// e.g., internal server error.
is ApiResponse.Failure.Error -> {
// stub error case
Log.d(Constants.TAG, "${this::class.simpleName} " +
"Error")
// handles error cases depending on the status code.
when (response.statusCode) {
StatusCode.InternalServerError ->
Log.d(Constants.TAG, "${this::class.simpleName} " +
"${response.statusCode}")
StatusCode.BadGateway ->
Log.d(Constants.TAG, "${this::class.simpleName} " +
"${response.statusCode}")
else ->
Log.d(Constants.TAG, "${this::class.simpleName} " +
"${response.statusCode}")
}
}
// handles exceptional cases when the API request gets an exception response.
// e.g., network connection error, timeout.
is ApiResponse.Failure.Exception -> {
// stub exception case
}
}
}
고차함수를 반환하는 Genetic 확장 함수를 이용한 코드
fun <T> Call<T>.enqueue(callback : (result : Response<T>?, msg : String) -> Unit) {
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
Log.d(
Constants.TAG, this@enqueue::class.java.simpleName +
"1 onResponse() called : ${response}")
when(response.code()){
200 ->{
callback.invoke(response, response.message())
}
else ->
callback.invoke(null, response.errorBody()?.string() ?: response.message())
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
Log.d(
Constants.TAG, this@enqueue::class.java.simpleName +
"1 onFailure() called : ${t}"
)
callback.invoke(null, t.message ?: "error!")
}
})
}
//use
weatherRepository.search_weather3(locationFormat, lang).enqueue { result, msg ->
...
}
Retrofit2 Interface의 반환 타입을 일시 중지 함수의 반환 유형인 ApiResponse<T>로 선택하여 Sandwich와 Coroutine을 이용한 코드
- Interface의 반환 타입을 ApiResponse<T>로 설정하려면 suspend 키워드가 꼭 필요하며, Retrofit2 Http Client를 생성하는 코드에 .addCallAdapterFactory(CoroutinesResponseCallAdapterFactory())를 추가해야합니다.
- 해당 코드가 사용되는 함수는 필수적으로 suspend 한정자가 추가되거나, 비동기 환경에서 사용되어야 합니다.
val response : ApiResponse<JsonElement> = weatherRepository.search_weather(locationFormat, lang)
response.onSuccess {
Log.d(Constants.TAG, "${this::class.simpleName} " +
"onSuccess ${data?.asJsonObject}")
data?.asJsonObject?.let { parsingWeatherJsonData(it) }
}.onError {
// stub error case
Log.d(Constants.TAG, "${this::class.simpleName} " +
"Error")
// handles error cases depending on the status code.
when (statusCode) {
StatusCode.InternalServerError ->
Log.d(Constants.TAG, "${this::class.simpleName} " +
"$statusCode")
StatusCode.BadGateway ->
Log.d(Constants.TAG, "${this::class.simpleName} " +
"$statusCode")
else ->
Log.d(Constants.TAG, "${this::class.simpleName} " +
"지정된 예시가 없는 에러 코드 : $statusCode")
}
}.onException {
Log.d(Constants.TAG, "${this::class.simpleName} " +
"exception : $exception / message : $message")
}
loadingLiveData.postValue(false)
해당 코드를 선택한 이유는 Koin을 이용해 모듈을 생성하고 의존성을 주입받고, Coroutine을 이용해 비동기 환경에서 작동할 수 있도록 처리하며, Sandwich 라이브러리로 응답에 대한 처리를 깔끔하게 정리할 수 있어 현재로선 해당 방법이 가독성이나, 구현하고자 하는 프로그램 코드를 기능별로 적절히 나눠 모듈(크게는 하나의 파일, 작게는 하나의 함수)화 시킬 수 있고 이로인한 수월한 관리가 이루어질 수 있어 채택!
'Android' 카테고리의 다른 글
👋Android Programming : SnackBar! 기본 사용법부터 커스텀까지 (0) | 2022.05.08 |
---|---|
👋Android Programming : TextInputLayout을 이용한 UI 디자인 (0) | 2022.03.21 |
RecyclerView) BaseAdapter, BaseViewMdoel, BaseModel, DiffUtil : 공통 어답터와 뷰홀더를 이용해 리사이클러뷰 사용하기 (0) | 2021.08.04 |
BottomNavigationView를 이용한 Fragment 전환 (0) | 2021.08.02 |
TabLayout + ViewPager2를 이용한 화면 구성 (0) | 2021.08.02 |