오늘은 원하는 지역의 방탈출 카페 목록을 지도에 보여주는 App을 만든다!
온라인 예약이 가능한 카페의 경우 '예약하기' 버튼을 통해 예약페이지로 이동할 수 있다.
또한, 카페의 정보를 친구들에게 공유해줄 수 도 있다!
언어 : Kotlin (100%)
환경 : Android Studio
사용 기술
API를 통해 데이터 통신을 하기 위해 Retrofit을 사용하였다.
Retrofit 사용법은 지난 포스팅에 정리해놓았다.
◾ 인터페이스 추가 - RoomsSerivece
interface RoomsService {
@GET(api 나머지 주소를 적어주세용)
fun getRoomsList(
@Query("query") query: String,
@Query("displayCount") displayCount:Int = 20
): Call<RoomsResponse>
}
◾ 데이터 클래스 추가 - RoomsModel
data class RoomsModel(
@SerializedName("id")
val id: Int,
@SerializedName("name")
val title: String,
@SerializedName("menuInfo")
val price: String,
@SerializedName("thumUrl")
val imgUrl: String,
@SerializedName("x")
val lng: Double,
@SerializedName("y")
val lat: Double,
@SerializedName("tel")
val tel: String,
@SerializedName("abbrAddress")
val address: String,
@SerializedName("bizhourInfo")
val hourInfo: String,
@SerializedName("hasNaverBooking")
val hasBooking: Boolean,
@SerializedName("naverBookingUrl")
val bookingUrl: String
) : Serializable
// Serializable을 import 하여 직렬화를 해주어야 한다.
◾ items라는 리스트로 묶었으니 DTO 추가 - data class
내가 받아오는 데이터는 3중으로 구성되어있다.
data class RoomsResponse(
@SerializedName("result")
var roomsResult: RoomsResult
)
data class RoomsResult(
@SerializedName("place")
var place : RoomsList
)
data class RoomsList(
@SerializedName("list")
// 이렇게 마지막 list에 담긴 아이템들은 RoomsModel 타입의 리스트로 저장한다.
val items: List<RoomsModel> = arrayListOf<RoomsModel>()
)
◾ 레트로핏 객체 생성 후 데이터 가져오기
// retrofit 구현
val retrofit = Retrofit.Builder().baseUrl(api 기본주소를 적어주세요)
.addConverterFactory(GsonConverterFactory.create())
.build()
// retrofit 생성
retrofit.create(RoomsService::class.java).also {
// 또한, 바로 생성한 인터페이스의 getRoomsList()를 실행한다
it.getRoomsList(query = (searchWord.replace(" ","") + " 방탈출카페"))
.enqueue(object : Callback<RoomsResponse> {
override fun onResponse(
call: Call<RoomsResponse>,
response: Response<RoomsResponse>
) {
if (response.isSuccessful.not()) {
// 실패 할 경우
binding!!.progressbar.visibility = View.INVISIBLE
Toast.makeText(
this@MainActivity,
"에러! 잠시 후 다시 시도해주세요.",
Toast.LENGTH_SHORT
).show()
return
}
// 통신 성공 시
response.body()?.let { dto ->
binding!!.progressbar.visibility = View.INVISIBLE
if (dto.roomsResult.place == null) {
binding!!.textField.error = "검색 결과가 없습니다. 또는 지역명을 확인해주세요."
return
}
// 데이터를 가지고 결과를 보여줄 Activity로 이동
val datalist = dto.roomsResult.place.items
val intent = Intent(this@MainActivity, ResultActivity::class.java)
val list: ArrayList<RoomsModel> = ArrayList<RoomsModel>(datalist)
intent.putExtra("datalist", list)
startActivity(intent)
}
}
override fun onFailure(call: Call<RoomsResponse>, t: Throwable) {
// 통신 실패 시
}
})
}
◾ api 신청 및 clientId 추가하기
api 사용신청은 네이버 API 사이트에서 신청하면 된다.
신청 완료 후 부여받은 clientId는 따로 values로 저장 후 meta-data에 추가해주어야한다.
// api_key.xml
<resources>
<string name="naver_map_Client_Id">부여받은 ClientID</string>
</resources>
// Manifest.xml
<meta-data
android:name="com.naver.maps.map.CLIENT_ID"
android:value="@string/naver_map_Client_Id" />
◾ 프로젝트에 Naver Map 추가하기
// build.gradle
allprojects {
repositories {
google()
jcenter()
maven("https://naver.jfrog.io/artifactory/maven/")
}
}
dependencies {
// 네이버 지도 SDK
implementation("com.naver.maps:map-sdk:3.11.0")
}
◾ layout에 mapView 추가
<com.naver.maps.map.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
이제 MainActivity에서 넘겨받은 데이터 list를 가지고 지도에 마킹을 해준다.
// 네이버 지도 준비하기
override fun onMapReady(map: NaverMap) {
naverMap = map
// 확대/축소 정도 조절!
naverMap.maxZoom = 18.0
naverMap.minZoom = 10.0
val uiSetting = naverMap.uiSettings
// true로 하면 기본 버튼
// 현재 위치 버튼
uiSetting.isLocationButtonEnabled = false
uiSetting.setLogoMargin(30, 0, 0, 230)
// 내가 만든 버튼으로 변경
currentLocationButton.map = naverMap
locationSource = FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE)
naverMap.locationSource = locationSource
setListWithdata(datalist)
}
// 데이터를 가지고 list를 넘겨주어라!
private fun setListWithdata(datalist: ArrayList<RoomsModel>) {
updateMarker(datalist)
viewPagerAdpater.submitList(datalist)
recyclerViewAdapter.submitList(datalist)
bottomSheetTextView.text = "${datalist.size}개의 결과보기"
}
// 받은 list 하나씩 지도에 마크 찍기
private fun updateMarker(rooms: List<RoomsModel>) {
rooms.forEach { room ->
val marker = Marker()
marker.position = LatLng(room.lat, room.lng)
marker.onClickListener = this
marker.map = naverMap
marker.tag = room.id
marker.icon = MarkerIcons.RED
}
}
// viewPager(CardView로 생성했음)를 넘길 때 마다
// 해당하는 item의 마커로 지도 이동
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val selectedRoomModel = viewPagerAdpater.currentList[position]
val cameraUpdate =
CameraUpdate.scrollTo(LatLng(selectedRoomModel.lat, selectedRoomModel.lng))
.animate(CameraAnimation.Easing)
naverMap.moveCamera(cameraUpdate)
}
})
// 지도 위 마커 클릭 시 해당하는 item의 viewPager로 이동
override fun onClick(overlay: Overlay): Boolean {
val selectedModel = viewPagerAdpater.currentList.firstOrNull() {
it.id == overlay.tag
}
selectedModel?.let {
val position = viewPagerAdpater.currentList.indexOf(it)
viewPager.currentItem = position
}
return true
}
viewPager의 아이템을 클릭 하거나, 하단 뷰의 아이템 클릭 시
장소 정보 Activity로 이동한다.
// 해당 장소의 데이터를 가지고 Activity 이동
private fun goDetailActivity(roomsModel: RoomsModel) {
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("data", roomsModel)
startActivity(intent)
}
// 공유하기 기능을 통해 장소 정보를 외부 앱으로 전달할 수 있음
private fun shareInfo(data: RoomsModel) {
val intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(
Intent.EXTRA_TEXT,
"[방탈출 고고씽!\uD83C\uDFC3] 지금 \"${data.title}\"를 확인해보세요.\n\uD83D\uDC49 https://m.place.naver.com/place/${data.id} "
)
type = "text/plain"
}
startActivity(Intent.createChooser(intent, null))
}
// 네이버 예약하기가 가능한 장소의 경우 예약페이지로 이동할 수 있음
private fun goBookingSite(dataUri: String) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(dataUri)
startActivity(intent)
}
방탈출 카페 정보는 api가 따로 있나요??