오늘은 API Call에 쓰이는 Retrofit2를 학습해보고 정리를 하려고 한다.
사용중인 서버가 없기 때문에 jsonplaceholder를 사용해서 Test할 예정이다.
1. 기본적인 사용법
(0) build.gradle에 dependencies 추가
dependencies {
...
implementation('com.squareup.retrofit2:retrofit:2.9.0')
implementation('com.squareup.retrofit2:converter-gson:2.9.0')
}
(1) Retrofit 객체를 생성한다.
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitInstance {
val BASE_URL = "https://jsonplaceholder.typicode.com"
val client = Retrofit
.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
fun getInstance() = client
}
(2) 반환 데이터를 보고, DTO를 셋업한다.
// https://jsonplaceholder.typicode.com/posts/1 호출 시 얻을 수 있는 값
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
위는 api 호출 시 반환 예시인데 이걸 보고, key의 Name과 value의 Type으로 DTO를 만들어주면 된다.
- key의 Name은 필요하다면 다르게해도 무관하지만,
data class Post(
val userId: Int,
val id: Int,
val title: String,
val body: String,
)
(3) Api interface 정의
import com.example.retrofittest.dto.Post
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
interface TestApi {
// [정적] post/1 호출
@GET("posts/1")
fun getPost1(): Call<Post>
// [동적] post/number 호출
@GET("posts/{number}")
fun getPostNumber(
@Path("number") number: Int
): Call<Post>
}
@GET 안에 {변수}를 통해서 동적으로 원하는 url을 호출할 수 있다.
이때는 @Path를 통해 number가 입력 받는 파라미터라고? 명시해야 하면 된다.
이제 준비는 모두 끝났다. 실제로 Activity에서 API를 호출해보자.
(4) Activity Api Call 로직 구현
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// (1)번에서 정의한 Retrofit 객체를 가져온 뒤, TestApi를 연동한다?
val api = RetrofitInstance.getInstance()
.create(TestApi::class.java)
// (3)번의 getPost1() 메소드 호출
api.getPost1().enqueue(object: Callback<Post> {
// 호출에 성공한 경우(단순히 호출의 성공 유무이므로, 404등 error가 발생한 경우도 포함된다)
override fun onResponse(call: Call<Post>, response: Response<Post>) {
// Response{protocol=h2, code=200, message=, url=https://jsonplaceholder.typicode.com/posts/1}
Log.d("API1", response.toString()) // 통신 결과
// Post(userId=1, id=1, title=..., body=...)
Log.d("API1", response.body().toString()) // data
}
// 호출에 실패한 경우
override fun onFailure(call: Call<Post>, t: Throwable) {
Log.d("API1", "fail")
}
})
// (3)번의 getPostNumber(number: Int) 메서드 호출
api.getPostNumber(10).enqueue(object: Callback<Post> {
// 호출에 성공한 경우(단순히 호출의 성공 유무이므로, 404등 error가 발생한 경우도 포함된다)
override fun onResponse(call: Call<Post>, response: Response<Post>) {
// number = 10 -> Response{protocol=h2, code=200, message=, url=https://jsonplaceholder.typicode.com/posts/10}
// number = 1000 ->Response{protocol=h2, code=404, message=, url=https://jsonplaceholder.typicode.com/posts/1000}
Log.d("API2", response.toString()) // 통신 결과
// number = 10 -> Post(userId=1, id=10, title=..., body=...)
// number = 1000 -> null
Log.d("API2", response.body().toString()) // data
}
// 호출에 실패한 경우
override fun onFailure(call: Call<Post>, t: Throwable) {
Log.d("API2", "fail")
}
})
}
위와 같이 작성하면 간단하게 API를 호출할 수 있었다.
- 아래의 결과는 어떻게 나올까?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val api = RetrofitInstance.getInstance()
.create(TestApi::class.java)
(1..4).forEach {
api.getPostNumber(it).enqueue(object: Callback<Post> {
override fun onResponse(call: Call<Post>, response: Response<Post>) {
Log.d("API$it", response.toString())
Log.d("API$it", response.body().toString())
}
override fun onFailure(call: Call<Post>, t: Throwable) {
Log.d("API$it", "fail")
}
})
}
}
- API Call은 각 Task에 시간이 얼마나 걸릴지 모르기 때문에 당연히 비동기적으로 수행된다.
따라서 API1 -> API2 -> API3 -> API4 이 순서로 결과값이 나오지 않는다.
순차적으로 Api Call은 하지만, 서버로 부터 데이터를 먼저 받는 로직의 onResponse가 먼저 실행되기 때문이다.
- 이게 왜 문제가 될까? 만약 아래와 같이 A라는 Api Call을 하고, 반환되는 response값에 따라 B라는 Api Call을 해야한다면 어떻게 해야한다면 지금같이 그냥 나열해서는 안될 것이다.
- 단순하게 생각하면 A라는 Api Call의 결과인 onResponse 내부에서 B를 다시 Api Call 하도록 작성하면 될 것이다.
api.getPostNumber(1).enqueue(object: Callback<Post> {
override fun onResponse(call: Call<Post>, response: Response<Post>) {
Log.d("API1", response.body().toString())
api.getPostNumber(2).enqueue(object: Callback<Post> {
override fun onResponse(call: Call<Post>, response: Response<Post>) {
Log.d("API2", response.body().toString())
api.getPostNumber(3).enqueue(object: Callback<Post> {
override fun onResponse(call: Call<Post>, response: Response<Post>) {
Log.d("API3", response.body().toString())
}
override fun onFailure(call: Call<Post>, t: Throwable) {
Log.d("API3", "fail")
}
})
}
override fun onFailure(call: Call<Post>, t: Throwable) {
Log.d("API2", "fail")
}
})
}
override fun onFailure(call: Call<Post>, t: Throwable) {
Log.d("API1", "fail")
}
})
하지만, 이런 로직이 점차 복잡해진다면 A -> B -> C -> D 코드를 계속해서 depth를 추가하며 넣어야 하고, 가독성도 매우 좋지않아 유지보수가 어려운 코드를 작성하게 된다.
그렇다면 어떻게 관리해야 효율적이고, 원하는 순서대로 api call을 수행할 수 있을까?
2. 간단한 Coroutine 예제 + ViewModelScope (CoroutineScope와 viewModelScope의 차이)
class SecondViewModel: ViewModel() {
fun a() {
CoroutineScope(Dispatchers.IO).launch {
getData("Test - CoroutineScope")
}
}
fun b() {
viewModelScope.launch {
getData("Test - viewModelScope")
}
}
suspend fun getData(tag: String) {
(1..15).forEach {
delay(1000)
Log.d(tag, it.toString())
}
}
}
CoroutineScope(a)와 viewModelScope(b) 는 어떤 차이가 있을까?
가정 1. getData 함수는 SecondView의 UI를 구성하기 위한 데이터를 가져오는 API Call 로직이다.
가정 2. a(), b()는 UI 구성을 위한 데이터 이므로 액티비티 진입 시 바로 호출된다.
사용자가 실수로 잘못 눌러서 secondActivity에 진입한다.
-> 이런 경우, 사용자는 바로 Back 버튼을 눌러 다시 이전화면으로 돌아가지만 CoroutineScope는 계속해서 돌아간다.
-> 즉, 불필요한 Task를 수행하고 있는 것이다. 이런 경우에는 별도로 코루틴을 중지해줘야 하는 코드를 추가해야한다.
코드 실행 결과
2024-04-14 17:01:28.516 10222-10222 Test - viewModelScope com.example.retrofittest D 1
2024-04-14 17:01:28.517 10222-10274 Test - CoroutineScope com.example.retrofittest D 1
2024-04-14 17:01:29.520 10222-10222 Test - viewModelScope com.example.retrofittest D 2
2024-04-14 17:01:29.520 10222-10274 Test - CoroutineScope com.example.retrofittest D 2
2024-04-14 17:01:30.521 10222-10274 Test - CoroutineScope com.example.retrofittest D 3
2024-04-14 17:01:30.523 10222-10222 Test - viewModelScope com.example.retrofittest D 3
2024-04-14 17:01:31.523 10222-10274 Test - CoroutineScope com.example.retrofittest D 4
2024-04-14 17:01:31.524 10222-10222 Test - viewModelScope com.example.retrofittest D 4
/ / 사용자가 Back Button을 눌러 이전 화면으로 돌아감.
2024-04-14 17:01:32.527 10222-10274 Test - CoroutineScope com.example.retrofittest D 5
2024-04-14 17:01:33.529 10222-10274 Test - CoroutineScope com.example.retrofittest D 6
2024-04-14 17:01:34.532 10222-10274 Test - CoroutineScope com.example.retrofittest D 7
2024-04-14 17:01:35.534 10222-10274 Test - CoroutineScope com.example.retrofittest D 8
2024-04-14 17:01:36.538 10222-10274 Test - CoroutineScope com.example.retrofittest D 9
2024-04-14 17:01:37.543 10222-10274 Test - CoroutineScope com.example.retrofittest D 10
2024-04-14 17:01:38.547 10222-10274 Test - CoroutineScope com.example.retrofittest D 11
2024-04-14 17:01:39.553 10222-10274 Test - CoroutineScope com.example.retrofittest D 12
2024-04-14 17:01:40.555 10222-10274 Test - CoroutineScope com.example.retrofittest D 13
2024-04-14 17:01:41.560 10222-10276 Test - CoroutineScope com.example.retrofittest D 14
2024-04-14 17:01:42.564 10222-10276 Test - CoroutineScope com.example.retrofittest D 15
SecondActivity가 destroy 되어 더이상 api로 부터 데이터를 받아올 필요가 없지만, 계속해서 가져오고 있는 CoroutineScope를 볼 수 있다.
-> viewModelScope는 SecondActivity가 destroy이 되고, viewModel이 필요 없어지면 자동으로 실행중인 코루틴을 취소한다.
최종적으로는 다른 api를 호출한 뒤 glide( string -> url -> ImageView에 적용)를 사용하여 ReclcyerView에 띄워봤다.
https://github.com/rlaxodud214/NBC-Challenge-Week4/tree/feat/Api-Call
interface SearchApi {
@GET("v2/search/image")
// @Headers("Authorization: ${BuildConfig.KAKAO_API_KEY}")
suspend fun getSearchImage(
@Header("Authorization") accessToken: String,
@Query("query") query: String,
): KaKaoSearchResponse
@GET("v2/search/image")
// @Headers("Authorization: ${BuildConfig.KAKAO_API_KEY}")
fun getSearchImageCall(
@Header("Authorization") accessToken: String,
@Query("query") query: String,
): Call<KaKaoSearchResponse>
}
위 로직을 이용하여 kakao API를 호출했지만, Head의 인증키 값이 누락되었다는 메세지가 출력된다.
fun setSearchImageDataCall() {
viewModelScope.launch {
val keyWord = _searchWord.value.toString()
repository.getSearchImageDataCall(keyWord).enqueue(object: Callback<KaKaoSearchResponse> {
override fun onResponse(
call: Call<KaKaoSearchResponse>,
response: Response<KaKaoSearchResponse>,
) {
Log.d("API Call", "response : ${response}")
Log.d("API Call", "headers() : ${call.request().headers()}")
Log.d("API Call", "header[Authorization] : ${call.request().headers("Authorization")}")
Log.d("API Call", "ContentType : ${call.request().headers("Content-Type")}")
}
override fun onFailure(call: Call<KaKaoSearchResponse>, t: Throwable) {
TODO("Not yet implemented")
}
})
}
}
2024-04-15 03:20:00.315 15469-15469 API Call com.example.retrofittest D KaKaoSearchResponse(meta=Meta(totalCount=-1, pageableCount=-1, isEnd=false), documents=[ImageDocument(title=, url=, datetime=2011-04-09T09:37:54.000+09:00), ImageDocument(title=, url=, datetime=2023-01-18T11:24:58.000+09:00), ImageDocument(title=, url=, datetime=2023-11-10T11:06:42.000+09:00), ImageDocument(title=, url=, datetime=2024-02-16T13:18:18.000+09:00), ImageDocument(title=, url=, datetime=2011-05-17T12:05:32.000+09:00), ImageDocument(title=, url=, datetime=2022-11-21T21:54:39.000+09:00), ImageDocument(title=, url=, datetime=2020-03-23T17:23:46.000+09:00), ImageDocument(title=, url=, datetime=2023-11-02T02:56:23.000+09:00), ImageDocument(title=, url=, datetime=2023-10-09T12:55:25.000+09:00), ImageDocument(title=, url=, datetime=2020-02-08T19:58:45.000+09:00), ImageDocument(title=, url=, datetime=2017-04-22T20:23:00.000+09:00), ImageDocument(title=, url=, datetime=2023-06-19T14:26:23.000+09:00), ImageDocument(title=, url=, datetime=2024-04-04T13:56:28.000+09:00), ImageDocument(title=, url=, datetime=2023-02-27T23:21:37.000+09:00), ImageDocument(title=, url=, datetime=2021-08-14T20:59:12.000+09:00), ImageDocument(title=, url=, datetime=2023-06-20T07:30:39.000+09:00), ImageDocument(title=, url=, datetime=2016-12-13T09:00:06.000+09:00), ImageDocument(title=, url=, datetime=2023-09-08T07:57:29.000+09:00), ImageDocument(title=, url=, datetime=2023-04-23T19:02:34.000+09:00), ImageDocument(title=, url=, datetime=2024-03-29T15:25:59.000+09:00), ImageDocument(title=, url=, datetime=2022-11-28T13:24:31.000+09:00), ImageDocument(title=, url=, datetime=2023-09-15T16:12:09.000+09:00), ImageDocument(title=, url=, datetime=2020-12-14T04:32:24.000+09:00), ImageDocument(title=, url=, datetime=2024-03-07T11:06:33.000+09:00), ImageDocument(title=, url=, datetime=2022-11-28T09:00:14.000+09:00), ImageDocument(title=, url=, datetime=2023-07-17T23:53:00.000+09:00), ImageDocument(title=, url=, datetime=2023-10-14T12:25:44.000+09:00), ImageDocument(title=, url=, datetime=2023-03-05T10:49:17.000+09:00), ImageDocument(title=, url=, datetime=2017-05-02T18:37:23.000+09:00), ImageDocument(title=, url=, datetime=2023-09-13T15:04:21.000+09:00), ImageDocument(title=, url=, datetime=2023-06-17T10:58:03.000+09:00), ImageDocument(title=, url=, datetime=2023-09-02T01:32:08.000+09:00), ImageDocument(title=, url=, datetime=2021-07-02T13:46:03.000+09:00), ImageDocument(title=, url=, datetime=2023-03-18T09:00:30.000+09:00), ImageDocument(title=, url=, datetime=2021-12-01T14:36:01.000+09:00), ImageDocument(title=, url=, datetime=2023-03-19T10:09:55.000+09:00), ImageDocument(title=, url=, datetime=2017-11-08T20:52:10.000+09:00), ImageDocument(title=, url=, datetime=2023-07-07T07:25:35.000+09:00), ImageDocument(title=, url=, datetime=2022-04-10T22:15:43.000+09:00), ImageDocument(title=, url=, datetime=2023-06-04T08:00:37.000+09:00), ImageDocument(title=, url=, datetime=2021-09-11T18:38:52.000+09:00), ImageDocument(title=, url=, datetime=2024-03-07T11:06:33.000+09:00), ImageDocument(title=, url=, datetime=2019-07-22T11:34:24.000+09:00), ImageDocument(title=, url=, datetime=2024-03-02T18:35:25.000+09:00), ImageDocument(title=, url=, datetime=2023-11-10T23:02:11.000+09:00), ImageDocument(title=, url=, datetime=2014-08-20T10:50:18.000+09:00), ImageDocument(title=, url=, datetime=2024-03-09T15:52:41.000+09:00), ImageDocument(title=, url=, datetime=2023-05-15T11:44:29.000+09:00), ImageDocument(title=, url=, datetime=2023-11-14T17:36:30.000+09:00), ImageDocument(title=, url=, datetime=2023-02-26T05:55:02.000+09:00), ImageDocument(title=, url=, datetime=2019-07-10T21:42:53.000+09:00), ImageDocument(title=, url=, datetime=2023-08-17T08:00:59.000+09:00), ImageDocument(title=, url=, datetime=2023-03-15T23:23:04.000+09:00), ImageDocument(title=, url=, datetime=2023-03-11T05:55:45.000+09:00), ImageDocument(title=, url=, datetime=2022-11-28T13:24:31.000+09:00), ImageDocument(title=, url=, datetime=2021-05-14T20:52:03.000+09:00), ImageDocument(title=, url=, datetime=2023-07-26T20:45:51.000+09:00), ImageDocument(title=, url=, datetime
2024-04-15 03:20:00.329 15469-15469 API Call com.example.retrofittest D response : Response{protocol=h2, code=200, message=, url=https://dapi.kakao.com/v2/search/image?query=%EA%B3%A0%EC%96%91%EC%9D%B4}
2024-04-15 03:20:00.330 15469-15469 API Call com.example.retrofittest D headers() : Authorization: KakaoAK {인증키}
2024-04-15 03:20:00.330 15469-15469 API Call com.example.retrofittest D header[Authorization] : [KakaoAK {인증키}]
2024-04-15 03:20:00.331 15469-15469 API Call com.example.retrofittest D ContentType : []
headers 출력 시 "Authorization: KakaoAK {인증키}"로 제대로 출력되지만, 제대로 인증되지 않았다는 아래와 같은 오류가 발생한다.
이유가 뭘까,,, 한 줄만 수정하면 될 것 같은데 해결이 되지 않고있다,,,
- 혹시 몰라 postman 에서 key 검증을 해봤다.
그 결과 key는 이상이 없었고, 아마 내가 코드에서 header를 제대로 전달하지 못한 것으로 확인된다.
object RetrofitInstance {
val BASE_URL: String
get() = BuildConfig.KAKAO_API_URL
val client = initInstance(BASE_URL)
fun getInstance() = client
fun initInstance(url: String) = Retrofit
.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
OkHttpClient를 사용해서 Header를 담는 방법이 있어 위의 코드를 아래로 수정해보았다.
object RetrofitInstance {
private val INSTANCE = initInstance()
fun getInstance() = INSTANCE
private fun initInstance(): Retrofit {
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.header("Authorization", BuildConfig.KAKAO_API_KEY)
.build()
chain.proceed(request)
}
.build()
return Retrofit.Builder()
.baseUrl(BuildConfig.KAKAO_API_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
이렇게 수정하니 디버그 로그가 아래와 같이 바꼈다. Header는 비어있는 것 같아 오히려 전의 코드가 좀 더 근접하다고 생각된다.
2024-04-15 03:59:56.044 17245-17245 API Call com.example.retrofittest D response : Response{protocol=h2, code=200, message=, url=https://dapi.kakao.com/v2/search/image?query=%EA%B3%A0%EC%96%91%EC%9D%B4}
2024-04-15 03:59:56.053 17245-17245 API Call com.example.retrofittest D headers() :
2024-04-15 03:59:56.053 17245-17245 API Call com.example.retrofittest D header[Authorization] :
2024-04-15 03:59:56.053 17245-17245 API Call com.example.retrofittest D ContentType : []
2024-04-15 03:59:56.055 17245-17245 API Call com.example.retrofittest D KaKaoSearchResponse(meta=Meta(totalCount=-1, pageableCount=-1, isEnd=false), documents=[ImageDocument(title=, url=, datetime=2011-04-09T09:37:54.000+09:00), ImageDocument(title=, url=, datetime=2023-01-18T11:24:58.000+09:00), ImageDocument(title=, url=, datetime=2023-11-10T11:06:42.000+09:00), ImageDocument(title=, url=, datetime=2024-02-16T13:18:18.000+09:00), ImageDocument(title=, url=, datetime=2011-05-17T12:05:32.000+09:00), ImageDocument(title=, url=, datetime=2022-11-21T21:54:39.000+09:00), ImageDocument(title=, url=, datetime=2020-03-23T17:23:46.000+09:00), ImageDocument(title=, url=, datetime=2023-11-02T02:56:23.000+09:00), ImageDocument(title=, url=, datetime=2023-10-09T12:55:25.000+09:00), ImageDocument(title=, url=, datetime=2020-02-08T19:58:45.000+09:00), ImageDocument(title=, url=, datetime=2017-04-22T20:23:00.000+09:00), ImageDocument(title=, url=, datetime=2023-06-19T14:26:23.000+09:00), ImageDocument(title=, url=, datetime=2024-04-04T13:56:28.000+09:00), ImageDocument(title=, url=, datetime=2023-02-27T23:21:37.000+09:00), ImageDocument(title=, url=, datetime=2021-08-14T20:59:12.000+09:00), ImageDocument(title=, url=, datetime=2023-06-20T07:30:39.000+09:00), ImageDocument(title=, url=, datetime=2016-12-13T09:00:06.000+09:00), ImageDocument(title=, url=, datetime=2023-09-08T07:57:29.000+09:00), ImageDocument(title=, url=, datetime=2023-04-23T19:02:34.000+09:00), ImageDocument(title=, url=, datetime=2024-03-29T15:25:59.000+09:00), ImageDocument(title=, url=, datetime=2022-11-28T13:24:31.000+09:00), ImageDocument(title=, url=, datetime=2023-09-15T16:12:09.000+09:00), ImageDocument(title=, url=, datetime=2020-12-14T04:32:24.000+09:00), ImageDocument(title=, url=, datetime=2024-03-07T11:06:33.000+09:00), ImageDocument(title=, url=, datetime=2022-11-28T09:00:14.000+09:00), ImageDocument(title=, url=, datetime=2023-07-17T23:53:00.000+09:00), ImageDocument(title=, url=, datetime=2023-10-14T12:25:44.000+09:00), ImageDocument(title=, url=, datetime=2023-03-05T10:49:17.000+09:00), ImageDocument(title=, url=, datetime=2017-05-02T18:37:23.000+09:00), ImageDocument(title=, url=, datetime=2023-09-13T15:04:21.000+09:00), ImageDocument(title=, url=, datetime=2023-06-17T10:58:03.000+09:00), ImageDocument(title=, url=, datetime=2023-09-02T01:32:08.000+09:00), ImageDocument(title=, url=, datetime=2021-07-02T13:46:03.000+09:00), ImageDocument(title=, url=, datetime=2023-03-18T09:00:30.000+09:00), ImageDocument(title=, url=, datetime=2021-12-01T14:36:01.000+09:00), ImageDocument(title=, url=, datetime=2023-03-19T10:09:55.000+09:00), ImageDocument(title=, url=, datetime=2017-11-08T20:52:10.000+09:00), ImageDocument(title=, url=, datetime=2023-07-07T07:25:35.000+09:00), ImageDocument(title=, url=, datetime=2022-04-10T22:15:43.000+09:00), ImageDocument(title=, url=, datetime=2023-06-04T08:00:37.000+09:00), ImageDocument(title=, url=, datetime=2021-09-11T18:38:52.000+09:00), ImageDocument(title=, url=, datetime=2024-03-07T11:06:33.000+09:00), ImageDocument(title=, url=, datetime=2019-07-22T11:34:24.000+09:00), ImageDocument(title=, url=, datetime=2024-03-02T18:35:25.000+09:00), ImageDocument(title=, url=, datetime=2023-11-10T23:02:11.000+09:00), ImageDocument(title=, url=, datetime=2014-08-20T10:50:18.000+09:00), ImageDocument(title=, url=, datetime=2024-03-09T15:52:41.000+09:00), ImageDocument(title=, url=, datetime=2023-05-15T11:44:29.000+09:00), ImageDocument(title=, url=, datetime=2023-11-14T17:36:30.000+09:00), ImageDocument(title=, url=, datetime=2023-02-26T05:55:02.000+09:00), ImageDocument(title=, url=, datetime=2019-07-10T21:42:53.000+09:00), ImageDocument(title=, url=, datetime=2023-08-17T08:00:59.000+09:00), ImageDocument(title=, url=, datetime=2023-03-15T23:23:04.000+09:00), ImageDocument(title=, url=, datetime=2023-03-11T05:55:45.000+09:00), ImageDocument(title=, url=, datetime=2022-11-28T13:24:31.000+09:00), ImageDocument(title=, url=, datetime=2021-05-14T20:52:03.000+09:00), ImageDocument(title=, url=, datetime=2023-07-26T20:45:51.000+09:00), ImageDocument(title=, url=, datetime
그래도 오류는 해결되지 않았다 ㅎㅎㅎㅎㅎㅎㅎ,,,,,,,,,, 내일 좀 더 보자..
다시 생각해보니, 그냥 url로 접속하면 당연히 header가 없기 때문에 위 처럼 오류가 발생하는 게 당연하다,,,,
data class ImageDocument(
val collection: String,
val url: String,
val datetime: String,
)
데이터를 출력할 때, datetime이 제대로 나오는 걸 뒤는 게 발견하고, 다시 Model을 보니 제대로 반환 파라미터를 받고 있지 않아 model을 아래와 같이 수정하니 데이터가 제대로 출력되는 걸 볼 수 있었다,,,,,,
-> Header는 제대로 전달이 되고 있었다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ,,,,,,
data class ImageDocument(
// 만약 변수명을 다르게 쓰고 싶다면 SerializedName을 사용해서 명시해주면 된다.
@SerializedName("collection")
val collection: String,
@SerializedName("datetime")
val datetime: String,
val display_sitename: String,
val doc_url: String,
val height: Int,
val width: Int,
val image_url: String,
val thumbnail_url: String,
)
api call 결과 code가 200인 걸 좀 더 생각해봤으면 삽질을 덜했을텐데,,, 아쉽다 ㅎㅎㅎㅎ
-> 내일은 간단하게 RecyclerView를 적용하고 프로젝트를 마치자
-> 구현완료
gitHub : https://github.com/rlaxodud214/NBC-Challenge-Week4
'Android' 카테고리의 다른 글
ch 5주차 정리 (0) | 2024.04.30 |
---|---|
앱 개발 숙련 과제 후기 (1) | 2024.04.18 |
Fragment의 정의, 사용법 및 데이터 전달 방식 (1) | 2024.04.12 |
RecyclerView의 Adapter 구현 및 Item Click Event 처리방법 정리 (0) | 2024.04.11 |
Adapter, AdapterView 및 ListView, GridView 정리 (0) | 2024.04.11 |