다음글 : https://small-stepping.tistory.com/603
https://small-stepping.tistory.com/586
위 글에 이어서 날씨 앱 개발 근황 및 기록 글이다.
추석 주엔 그냥 쭉 쉬었다. 큰 집에 길게 다녀오기도 했고
기존 학부생때는 Java를 사용해서 앱을 만들었었고, 졸업 시기쯤 Kotlin으로 언어를 변경하였다. 사실 그 둘은 굉장히 긴밀한 관계이기에 거기서 거기긴 하지만, 학사를 땄다한들 제대로 공부가 되어있지 않아 쉽지 않은 것은 매한가지였다.
상당히 골머리를 썩혔던 구간은 http 통신을 통해 API를 사용하는 부분이었다.
Java를 쓸 때 API는 data.go.kr의 만드는 주제와 맞는 API에 쓰여있는 코드를 가져와서 그대로 버그만 수정해서 썼었기에 코드가 이쁘건 안이쁘건 데이터가 파싱해오기만 했으면 됐었다.
근데 그 시절 작성한 코드를 보면 불필요한 부분도 많고 이렇게 써서 코드가 작동했다는게 놀라운 수준이었다. 영리하게 작성할 필요가 존재했고, 그 존재가 오픈 라이브러리들이다.
Retrofit2를 사용한 통신에 집중했고, 코드를 역할에 따라 함수를 나눠 코드를 짧고 읽기 쉽게 만들기로 했다.
Dao나 Interface, DataClass등 따로 Retrofit2 통신을 위해 필요한 코드는 더 있으나 그걸 제외하고 통신해서 데이터를 가져가는 부분은 저걸로 정리되었다. 이전에 토이프로젝트로 잠시 만져봤던 LostArk API에서 작성한 코드를 그대로 가져와서 수정했으나 그 부분에서 상당한 문제가 발생했고, 코드를 상당부분 수정했다.
발생했던 에러와 수정은 다음과 같다.
1. Error Code 01 Application Error
- 기존에 사용하던 @GET 방식을 @POST, Response -> Call 로 변환하니 해결됨, 이후 2, 3번 에러 발생
2. Unable to invoke no-args constructor for retrofit2.Call
3. Registering an InstanceCreator with Gson for this type may fix this problem.
- interface의 @GET 부분 함수에 suspend fun -> fun으로 변경, 4번 에러 발생
4. Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
- 다시 api가 요구하는 dto, response를 맞춰보았으나 실패.
- http 통신 로그를 찍어봐야겠다는 생각이 들어 okhttp, logging을 추가하고 로그 확인.
- 로그로 나오는 주소에 들어가보니 authKey 에러 발생. 기존에 사용하던 encoding키에서 decoding 키로 변경
- 5번 에러 발생
5. SocketTimeoutException: failed to connect to apis.data.go.kr/아이피 (port 80) from / 아이피 (port 0000) after 10000ms
- authKey 변경 결과 로그에 찍히는 주소에 들어갔을 때 잘 나옴, 하지만 앱에서는 오류 발생.
- timeout 에러가 발생한 것이니, retrofit2 build 코드에서 연결, 읽기, 쓰기에 timeout 코드 추가
- 6번 에러 발생
fun getService(): Service {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BASIC
// 이 부분 추가
val oKHttpClient = OkHttpClient.Builder()
.addInterceptor((httpLoggingInterceptor))
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(oKHttpClient)
.build()
.create(Service::class.java)
}
6. Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 107 path $.response.body.items
- 다시 4번과 유사한 에러 발생.
- 받아오는 response를 재확인 하고 잘못된 부분을 수정.
- body에 item<List>로 있는 줄 알았는데, body - items - item<List> 였다.
- 7번 에러 발생
7. lateinit property resultultra has not been initialized
- api 1개에만 접근하면 문제 없음
- api 1개의 정보를 받고 내부에서 바로 response 데이터를 가공하면 문제없음
- response 데이터를 기존에 추가해둔 lateinit var data로 초기화 해주고 다른 함수에서 사용시 문제 발생
이 때 코루틴스코프와 withContext 내부에서 통신하는 방식이 잘못되었다는 생각이 들어 api 통신이 전부 끝나고 넘어가는 방법을 강구하게 됨. (기존에는 각 api마다 callback이 나눠져 있었음)
해결 방법 : https://m.blog.naver.com/puj98/222625398724
1, 2, 3번 에러로 추가하고 수정했던 기존 방식인 POST, Call을 제거
// 기존 방식
@POST("1360000/VilageFcstInfoService_2.0/getUltraSrtNcst")
suspend fun getUltraSrtNcst(
@Query("serviceKey") authKey: String,
@Query("pageNo") pageNo: Int,
@Query("numOfRows") numOfRows: Int,
@Query("dataType") dataType: String,
@Query("base_date") baseDate: String,
@Query("base_time") baseTime: String,
@Query("nx") nx: Int,
@Query("ny") ny: Int
): Response<UltraSrtNcstData.UltraSrtNcst>
//변경된 방식
@GET("1360000/VilageFcstInfoService_2.0/getUltraSrtNcst")
suspend fun getUltraSrtNcst(
@Query("serviceKey") authKey: String,
@Query("pageNo") pageNo: Int,
@Query("numOfRows") numOfRows: Int,
@Query("dataType") dataType: String,
@Query("base_date") baseDate: String,
@Query("base_time") baseTime: String,
@Query("nx") nx: Int,
@Query("ny") ny: Int
): UltraSrtNcstData.UltraSrtNcst
또한, 각 api 통신마다 async를 추가하고 awaitAll을 추가.
job = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
withContext(Dispatchers.Main) {
try {
val response1 = async { ... }
val response2 = async { ... }
val response3 = async { ... }
val response4 = async { ... }
awaitAll(response1, response2, response3, response4).let {
loadError.postValue("")
loading.postValue(false)
setupView(binding, timeList)
}
} catch (e: HttpException) { e.printStackTrace()
} catch (e: Throwable) { e.printStackTrace()
}
}
}
결과적으로 정상적으로 데이터 전부 받아와서 가공할 수 있게 되었다.
'개발 > AOS' 카테고리의 다른 글
날씨 앱 기록 03 (1) | 2023.10.20 |
---|---|
날씨 앱 기록 02 (2) | 2023.10.13 |
kapt에서 KSP로 이전 (0) | 2023.10.05 |
AndroidStudio에서 API Key, Auth Key 관리하기 (0) | 2023.09.19 |
카카오 맵 API를 사용해 좌표평면상 두 점간의 거리를 구했던 이야기 (0) | 2023.03.21 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!