이번에는 간단하게 테스트용 API를 Json형식으로 만든 후 사용하는 방법과, 글라이드와 레트로핏을 사용하여 받아오는걸 해보도록 하겠다.
# Mocky
아래 페이지에 들어간다.
NEW MOCK을 클릭하고, 난 API요청(GET)을 보내 정보를 받아올거이기 때문에 Response Body에 받아올 데이터를 미리 만들어 붙여넣으면 api주소를 주는데 그곳에서 내가 작성했던 내용 그대로를 response로 보내준다.
아래와 같이 json형식으로 mock을 만들어주자.
{
"items" : [
{
"id" : 1,
"title" : "강남역 주변, COZY한 집",
"price" : "50,000원",
"lat" : 37.4978028,
"lng" : 127.0282324,
"imgUrl" : "https://loremflickr.com/cache/resized/65535_51753613651_7e4eeca87b_n_200_200_nofilter.jpg"
},
{
"id" : 2,
"title" : "강남역 주변, 편안한 집",
"price" : "50,000원",
"lat" : 37.497156,
"lng" : 127.027267,
"imgUrl" : "https://loremflickr.com/cache/resized/65535_51519263196_8529f860f7_n_200_200_nofilter.jpg"
},
{
"id" : 3,
"title" : "강남역 주변, 나쁜 집",
"price" : "40,000원",
"lat" : 37.4982113,
"lng" : 127.0267947,
"imgUrl" : "https://loremflickr.com/cache/resized/65535_51753613651_7e4eeca87b_n_200_200_nofilter.jpg"
},
{
"id" : 4,
"title" : "강남역 주변, 착한 집",
"price" : "20,000원",
"lat" : 37.496892,
"lng" : 127.0296915,
"imgUrl" : "https://loremflickr.com/cache/resized/65535_51488712625_59d63bc8b5_m_200_200_nofilter.jpg"
},
{
"id" : 5,
"title" : "강남역 주변, 좋은 집",
"price" : "30,000원",
"lat" : 37.4999988,
"lng" : 127.0291014,
"imgUrl" : "https://loremflickr.com/cache/resized/65535_52111075718_324aff5091_m_200_200_nofilter.jpg"
}
]
}
# 레트로핏, 글라이더 사용
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//glide
implementation 'com.github.bumptech.glide:glide:4.13.2'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
늘 하던대로 retrofit과 glide를 추가해준다. 이번에 SW중심대학 해커톤에서 레트로핏을 주구장창 사용해서 꽤 익숙해져 버렸다..
// MainActivity.kt
// create Retrofit
retrofit = Retrofit.Builder()
.baseUrl("https://run.mocky.io/")
.addConverterFactory(GsonConverterFactory.create())
.build()
private fun getHouseDataAPI() {
startProgressbar()
retrofitService = retrofit.create(RetrofitService::class.java)
retrofitService.getHouseData().enqueue(object : Callback<HouseDto>{
override fun onResponse(call: Call<HouseDto>, response: Response<HouseDto>) {
if(response.isSuccessful.not()) {
Toast.makeText(this@MainActivity,"데이터 응답 실패",Toast.LENGTH_SHORT).show()
return
}
}
override fun onFailure(call: Call<HouseDto>, t: Throwable) {
// 실패 처리
}
})
}
레트로핏을 build해주고, 레트로핏서비스를 만들어줘야 한다. 레트로핏 서비스 인터페이스에서는 GET,POST등과의 요청 및 url 그리고 어떤 데이터를 받아오는지를 알려주는 역할을 한다.
// RetrofitService.kt
interface RetrofitService {
@GET("여기에 baseURL뒤의 주소를 입력")
fun getHouseData() : Call<HouseDto> //응답받는 데이터 DTO
}
아까 우리가 작성한 Mock API에서 items : [...] 안에 정보를 담아 보내줬다.
//HouseDto.kt
data class HouseDto(
@SerializedName("items") val items : List<HouseData>
)
// HouseData.kt
data class HouseData(
@SerializedName("id") val id : Int,
@SerializedName("title") val title : String,
@SerializedName("price") val price : String,
@SerializedName("lat") val lat : Double,
@SerializedName("lng") val lng : Double,
@SerializedName("imgUrl") val imgUrl : String,
)
그리고 그렇게 받아온 리스트 안에는 HouseData안에 있는것들을 최종 사용하게 될 것이다.
# ViewPager2 어댑터
자세한 코드 설명은 생략하도록 하겠다.
class HouseViewPagerAdapter(val pageClickedCallback : (HouseData)-> Unit) : ListAdapter<HouseData, HouseViewPagerAdapter.ItemViewHolder>(differ){
inner class ItemViewHolder(val view : View) : RecyclerView.ViewHolder(view){
fun bind(house : HouseData){
val title = view.findViewById<TextView>(R.id.titleTextView)
val price = view.findViewById<TextView>(R.id.priceTextView)
val image = view.findViewById<ImageView>(R.id.thumbnailImageView)
view.setOnClickListener {
pageClickedCallback(house)
}
title.text = house.title
price.text = house.price
Glide.with(image.context)
.load(house.imgUrl)
.into(image)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ItemViewHolder(inflater.inflate(R.layout.item_house_viewpager,parent,false))
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val differ = object : DiffUtil.ItemCallback<HouseData>(){
override fun areItemsTheSame(oldItem: HouseData, newItem: HouseData): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: HouseData, newItem: HouseData): Boolean {
return oldItem == newItem
}
}
}
}
# 리사이클러뷰 어댑터
class HouseRecyclerViewApdater : ListAdapter<HouseData, HouseRecyclerViewApdater.ItemViewHolder>(differ){
inner class ItemViewHolder(val view : View) : RecyclerView.ViewHolder(view){
fun bind(house : HouseData){
val title = view.findViewById<TextView>(R.id.titleTextView)
val price = view.findViewById<TextView>(R.id.priceTextView)
val image = view.findViewById<ImageView>(R.id.thumbnailImageView)
title.text = house.title
price.text = house.price
// centerCrop -> 비트맵을 수정후 view에 그림
// 겉 부분 둥글게 하는법, 기본단위 px이기때문에 px-> DP 로 바꿔야함
Glide.with(image.context)
.load(house.imgUrl)
.transform(CenterCrop(), RoundedCorners(dpToPx(image.context,12)))
.into(image)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ItemViewHolder(inflater.inflate(R.layout.item_house_detail_rc,parent,false))
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(currentList[position])
}
private fun dpToPx(context: Context, dp:Int) : Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp.toFloat(),context.resources.displayMetrics).toInt()
}
companion object {
val differ = object : DiffUtil.ItemCallback<HouseData>(){
override fun areItemsTheSame(oldItem: HouseData, newItem: HouseData): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: HouseData, newItem: HouseData): Boolean {
return oldItem == newItem
}
}
}
}
리사이클러뷰에서는 받아온 이미지에 radius와 꽉차게 해줘야 한다. Glide의 transform속성을 이용하면 쉽게 바꿔 줄 수 있다.
.transform(CenterCrop(), RoundedCorners(dpToPx(image.context,12)))
RoundedCorners를 통해서 radius값을 줄 수 있는데, 대신 기본 px값으로 설정이 되기때문에 기기마다 다른 크기가 잡힐 것이다. 그렇기 때문에 dp를 px로 바꿔주는 함수를 따로 작성해 사용한다.
private fun dpToPx(context: Context, dp:Int) : Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp.toFloat(),context.resources.displayMetrics).toInt()
}
# Adpater 연동
작성한 어댑터들을 뷰의 어댑터 속성에 할당해주고, submit이 필요한 지점에 알맞게 submitList를 해준다.
housePageApdater.submitList(dto.items)
houseRecyclerViewApdater.submitList(dto.items)
# 끝내며
Mocky는 아주 유용하게 쓸 수 있을 것 같다. 이번 에어비앤비 비스무리하게 만드는 프로젝트에서는 딱히 어려운 기능이 별로 없는거 같다. 아키텍처 공부하려고 책도 샀는데 얼른 아키텍처 공부하고 싶다.
이번에 SW중심대학 해커톤 참가해서 상탔는데 그것도 블로그에 후딱 정리해야하 겠다.