책 리뷰 어플 개발하기
- ReclyclerView
- View Binding
- Retrofit
- Gilde
- Room Database
- OpenAPI
API test
포스트만을 다운 받은 뒤 인터파크에서 베스트 셀러 정보, 책검색 정보를 받기위한 GET method 호출
- 베스트 셀러
요청 url : http://book.interpark.com/api/bestSeller.api
파라미터
- key: key, value: 인증키
- key: categoryId, value : 100(한국 작품 카테고리)
- key: output, value: json
- 책 검색
요청 url : http://book.interpark.com/api/search.api
파라미터
- key: key, value: 인증키
- key: query, value : 검색 할 책 이름
- key: output, value: json
요청, 반환 확인
Retrofit
위에서 사용했던 RestAPI http 프로토콜 통신을 Android 앱에서 할 수 있게 지원해주는 라이브러리이다.
- dependcy 추가
Retrofit 공식문서를 참고해 dependcy를 추가해보자
레트로핏 사용을 위해 아래 줄 추가
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
레트로핏에서 지원해주는 gson 컨버터 라이브러리 추가
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
Json(Javascript Object Notation) 은 직렬화된 데이터를 받아오기 때문에 안드로이드에서 사용하기 위해 구글의 Gson 으로 직렬화된 json 데이터를 오브젝트로 변환해줘야함
- 인터넷 퍼미션 추가
Manifest.xml
파일에 인터넷 사용권한을 추가해야함(http 통신)
<uses-permission android:name="android.permission.INTERNET"/>
- model package 생성, Book Model, Dto 생성
위 Postman 에서 보았던 GET호출의 반환 값 중 받아올 데이터의 Dto, Model 클래스를 생성
package com.jyh.bookreview.model
import com.google.gson.annotations.SerializedName
// 베스트 셀러 호출 반환값을 받아옴
data class BestSellerDTO (
@SerializedName("title") val title:String,
@SerializedName("item") val books:List<Book>
)
package com.jyh.bookreview.model
import com.google.gson.annotations.SerializedName
// 이름 검색 호출 반환값을 받아옴
data class SearchBookDTO (
@SerializedName("title") val title:String,
@SerializedName("item") val books:List<Book>
)
package com.jyh.bookreview.model
import com.google.gson.annotations.SerializedName
// 위 호출들의 반환값 중 북 리스트의 북 모델
data class Book(
@SerializedName("itemId")val id:Long,
@SerializedName("title")val title:String,
@SerializedName("description")val description:String,
@SerializedName("coverSmallUrl")val coverSmallUrl:String
)
- Retrofit 객체 생성
MainActivity 의 onCreate 밑에 initRetrofit() 생성
private fun initRetrofit() {
// 레트로핏 객체 생성 패턴
val retrofit = Retrofit.Builder()
.baseUrl("https://book.interpark.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
// 서비스 생성
val bookService = retrofit.create(BookService::class.java )
// 서비스 중 베스트 셀러 호출
bookService.getBestSeller("인증키")
.enqueue(object: Callback<BestSellerDTO>{
override fun onResponse(
call: Call<BestSellerDTO>,
response: Response<BestSellerDTO>
) {
// 호출 실패 경우 반환
if(response.isSuccessful.not()) {
return
}
// 호출 성공시 행동, response.body(): BestSellerDTO
response.body()?.let{ it:BestSellerDTO->
// 로그 찍기
Log.d(TAG,it.toString())
it.books.forEach{ book->
Log.d(TAG,book.toString())
}
// 리사이클러뷰 어뎁터 구현, 생성 후 추가
// adapter.submitList(it.books)
}
}
override fun onFailure(call: Call<BestSellerDTO>, t: Throwable) {
Log.e(TAG,t.toString())
}
}
)
- 리사이클러뷰 어뎁터 구현
adapter 패키지를 생성하고 그 밑에 ListAdapter
상속받아 BookAdapter
를 생성한다.
어뎁터에 따로 리스트를 받지 않아도 currentList
멤버에 저장되어있다.
현재는 bind() 기능을 기본적으로 책 이름만 표시하지만 내용과 썸네일을 추가하자.
리사이클러뷰와 스크롤뷰의 차이
스크롤뷰의 경우 미리 전체 스크롤을 그린다.
스크롤이 매우 길어질 경우 앱이 죽거나 느려질 수 있다.
리사이클러뷰는 화면에 들어오는 리스트만 그린다.
BookAdapter
package com.jyh.bookreview.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.jyh.bookreview.databinding.ItemBookBinding
import com.jyh.bookreview.model.Book
// 상속 클래스 타입 인자로 리스트에 들어각 model, 생성 어뎁터의 ViewHolder, 인자로 diffUtil 객체를 받는다.
class BookAdapter:ListAdapter<Book, BookAdapter.BookItemViewHolder>(diffUtil){
// 내부 클래스로 ViewHolder 생성, 인자로 레이아웃 바인딩 객체를 받음 (레이아웃 생성시 자동으로 클래스가 만들어짐)
inner class BookItemViewHolder(private val binding: ItemBookBinding) :RecyclerView.ViewHolder(binding.root) {
// 바인드 함수 생성
fun bind(bookModel:Book){
binding.titleTextView.text = bookModel.title
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookItemViewHolder {
// BookItemViewHolder를 반환
// ItemBookBinding.inflate()을 통해 binding 생성
// LayoutInflater.from(parent.context) 를 통해 parent:ViewGroup 의 context 사용
return BookItemViewHolder(ItemBookBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
// viewHolder의 bind() 함수 호출
override fun onBindViewHolder(holder: BookItemViewHolder, position: Int) {
holder.bind(currentList[position])
}
// DiffUtil 객체를 생성, 리스트 속 객체가 같은지 구분하는 함수를 구현
companion object{
val diffUtil = object: DiffUtil.ItemCallback<Book>(){
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem==newItem
}
override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem.id == newItem.id
}
}
}
}
-
- viewbinding 사용
MainActivity에 binding 객체를 선언
...
lateinit var binding:ActivityMainBinding
...
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
...
-
- 레이아웃 생성
레이아웃 생성 시
레이아웃파일 이름 -> camel Case+Binding 클래스가 자동으로 생성됨.
위 리사이클러뷰 어뎁터 구현에서 사용된 ItemBookBinding
클래스는 자동으로 생성된 클래스
item_book.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/titleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- MainActicity에서 RecyclerView 구현
...
lateinit var adapter:BookAdapter
...
initBookRecyclerView()
...
private fun initBookRecyclerView() {
adapter = BookAdapter()
binding.bookRecyclerView.adapter = adapter
binding.bookRecyclerView.layoutManager = LinearLayoutManager(this)
}