Side Project - Book Review App
인터파크 도서 Open API를 사용해서 베스트 셀러 목록을 보여주고 검색을 통해 책을 검색하는 도서 리뷰 앱 프로젝트
학습 회고
오늘은 인터파크 도서 Open API를 활용해서 베스트셀러 리스트를 보여주고 책을 검색해서 관련 도서를 보여주는 도서 리뷰 앱을 만드는 프로젝트를 시작하였다.
Postman이라는 구글 크롬 앱을 통해 API 테스트를 해보며 요청과 결과 파라미터를 확인하였다.
또한, Retrofit 라이브러리를 활용해서 HTTP 통신으로 요청을 보내고 결과를 받아와 로그창에서 확인해보았다.
오늘 공부한 내용
- Retrofit2 라이브러리
Retrofit2는 스퀘어에서 만든 RESTful한 HTTP 통신을 간편하게 만들어 주는 라이브러리다.
또한, Annotation(@)을 사용하기 때문에 코드의 가독성이 좋다는 장점이 있다.
Retrofit은 네트워크 통신 정보만 제공하면 네트워크 프로그래밍을 대신 구현해 준다.
Retrofit으로 통신을 하려면 통신용 함수를 정의한 interface를 만들어야 한다.
interface의 함수를 호출해서 최종적으로 통신이 이뤄지지만 interface에는 함수를 선언만 하는 것이며 통신할 때 필요한 코드는 담겨 있지 않다. 주로 interface에 HTTP CRUD 메서드를 정의한다.
Retrofit에 생성한 interface를 알려 주면 Retrofit이 interface 정보를 보고 실제 통신에 필요한 코드를 담은 서비스 객체를 만들어 준다.
서비스 객체는 interface에 선언한 함수를 그대로 포함하며, 함수를 호출하면 Call 객체를 반환해준다.
이렇게 반환된 Call 객체의 enqueue() 함수를 호출하는 순간 통신을 수행한다.
- Retrofit2 사용하기
1. 라이브러리 선언하기
모듈(앱 단위) gradle의 dependencies 항목에 Retrofit2 사용에 필요한 라이브러리를 등록한다.
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
converter-gson은 JSON형태의 데이터를 직렬화를 통해 모델 객체로 변환하기 위해서 사용한다.
2. 모델 클래스 선언하기
모델 클래스는 서버와 주고받는 데이터를 표현하는 클래스이다.
JSON 타입 변환에 사용할 객체인 DTO(Data Transfer Object)를 작성한다.
원래는 JSON 데이터를 직접 코드에서 파싱해서 이용해야 하는데, 모델 클래스를 정보만 알려주면 모델 클래스의 객체를 알아서 생성하고 그 객체에 데이터를 담아준다.
모델 클래스의 property에 데이터가 자동으로 저장되기 위해서는 기본적으로 JSON 데이터의 key와 모델 클래스의 property 이름을 매칭해야한다. 만약, 다르게 정의하고 싶다면 @SerializedName이라는 Annotation을 명시하면 된다.
data class Book(
@SerializedName("itemId") val id: Long,
@SerializedName("title") val title: String,
@SerializedName("description") val description: String,
@SerializedName("coverSmallUrl") val coverSmallUrl: String
)
@SerializedName("itemId") val id: Long은 서버에서 받아온 JSON 객체의 itemId라는 key의 데이터가 id property에 저장된다는 의미이다.
* last_name이라는 key의 데이터를 lastName property에 저장하고 싶은 경우에는 @SerializedName Annotation을 사용하지 않아도 된다.
밑줄 다음에 오는 단어의 첫 글자를 대문자로 바꾼 property에 자동으로 저장되기 때문이다.
val lastName: String
//@SerializedName("last_name") val lastName: String
도서 리뷰 앱에서는 베스트셀러 목록을 받아오는 API와 키워드를 검색해서 관련 책들의 정보를 받아오는 API를 사용할 예정이기 때문에, Book 모델을 리스트 형식으로 받아오기 위해 BestSellerDto와 SearchBookDto 모델을 분리해서 만들어 주었다.
data class BestSellerDto(
@SerializedName("title") val title: String,
@SerializedName("item") val books: List<Book>
)
data class SearchBookDto(
@SerializedName("title") val title: String,
@SerializedName("item") val books: List<Book>
)
3. 통신용 함수를 선언한 interface를 작성한다.
Retrofit을 이용할 때 네트워크 통신이 필요한 순간에 호출할 함수를 포함하는 interface를 작성한다.
작성한 interface를 구현해서 실제로 통신하는 클래스는 Retrofit이 만들어 주는데 이때 Annotation을 보고 그 정보 대로 네트워크 통신을 할 수 있는 코드를 만들어 준다.
interface BookService {
@GET("/api/search.api?output=json")
fun getBooksByName(
@Query("key") apiKey: String,
@Query("query") keyword: String
): retrofit2.Call<SearchBookDto>
@GET("/api/bestSeller.api?output=json&categoryId=100")
fun getBestSellerBooks(
@Query("key") apiKey: String
): retrofit2.Call<BestSellerDto>
}
@GET은 GET 방식으로 서버와 연동하라는 것이고, @Query는 서버에 전달되는 데이터를 말한다.
도서 리뷰 앱에서는 책 이름을 검색해 관련 도서 리스트를 받아오는 getBooksByName과 베스트 셀러 목록을 가져오는 getBestSellerBooks 함수를 interface에 선언하였다.
4. Retrofit 객체를 생성하고 1.에서 선언한 interface를 전달하면 Retrofit이 통신용 서비스 객체를 반환한다.
- baseUrl() - Retrofit 객체에 baseUrl을 설정하면 인터페이스에 선언한 통신 메서드의 경로가 baseUrl 뒤에 지정되어 서버와 연동할 수 있다.
- addConverterFactory() - 데이터를 파싱해 모델 객체에 담는 역할자를 지정한다.
- create() - 서비스 인터페이스를 구현한 클래스의 서비스 객체를 생성한다.
val retrofit = Retrofit.Builder()
.baseUrl("https://book.interpark.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
val bookService = retrofit.create(BookService::class.java)
5. 서비스의 통신용 함수를 호출하여 Call 객체를 반환하고, Call 객체의 enqueue() 함수를 호출하여 네트워크 통신을 수행한다.
통신이 필요한 순간에 Retrofit 객체로 얻은 서비스 객체의 함수를 호출한다.
함수를 호출하면 Call 객체가 반환되고, 이 Call 객체의 enqueue() 함수를 호출하는 순간 통신이 이뤄진다.
- enqueue() - 매개변수인 Callback 객체의 onResponse(), onFailure() 함수가 자동으로 호출된다.
- onResponse() - 통신에 성공하면 서버에서 넘어온 데이터가 Response 객체로 전달된다. 이 Response 객체의 body() 함수를 호출해서 서버에서 받아온 데이터를 받아올 수 있다.
- onFailure() - 통신에 실패하면 호출된다. Throwable 매개변수를 통해 로그를 찍어 오류를 확인 할 수 있다.
bookService.getBestSellerBooks(API_KEY)
.enqueue(object: Callback<BestSellerDto> {
override fun onResponse(
call: Call<BestSellerDto>,
response: Response<BestSellerDto>
) {
if (response.isSuccessful.not()) {
Log.e(TAG, "NOT!! SUCCESS")
return
}
response.body()?.let {
Log.d(TAG, it.toString())
it.books.forEach { book ->
Log.d(TAG, book.toString())
}
}
}
override fun onFailure(call: Call<BestSellerDto>, t: Throwable) {
Log.e(TAG, t.toString())
}
})
}
참고
- Do it 깡샘의 안드로이드 앱 프로그래밍 with 코틀린 - 저자 강성윤
'Android > Side Projects' 카테고리의 다른 글
도서 리뷰 앱 개발하기 (0) | 2022.02.12 |
---|---|
도서 리뷰 앱 개발하기 (0) | 2022.02.11 |
My Alarm 앱 개발하기 (0) | 2022.02.09 |
My Alarm 앱 개발하기 (0) | 2022.02.08 |
오늘의 명언 앱 개발하기 (1) | 2022.02.07 |