[안드로이드&코틀린] 에어비앤비#1, 네이버 mapAPI 사용하기, 코디네이터(CoordinatorLayout)레이아웃, bo
에어비앤비와 유사한 앱을 만들어보았다. BottomSheetDialog 역할을 하는 view와 네이버 mapAPI, viewpager2등을 사용했다. 무엇보다 안드로이드 실제 디바이스를 당근마켓에서 구입해서 하고있는데, 빠릿
devforyou.tistory.com
이번에는 간단하게 테스트용 API를 Json형식으로 만든 후 사용하는 방법과, 글라이드와 레트로핏을 사용하여 받아오는걸 해보도록 하겠다.
# Mocky
아래 페이지에 들어간다.
Mocky: The world's easiest & fastest tool to mock your APIs
designer.mocky.io
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중심대학 해커톤 참가해서 상탔는데 그것도 블로그에 후딱 정리해야하 겠다.