1. 프로젝트 생성하고 의존성 추가하기
서울 공공도서관 앱은 지도 정보가 필요하므로 Google Maps Activity를 사용
AndroidManifest.xml
- API 키 입력
- 인터넷 접근 권한 추가
- 도서관 정보 API가 보안 프로토콜인 HTTPS가 아니라 HTTP를 사용하기 때문에 application 태그에 usesCleartextTraffic 속성 추가
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="API_KEY 입력" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true"
build.gradle
- 레트로핏과 viewBinding 설정
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
buildFeatures {
viewBinding true
}
2. 데이터 클래스 생성
웹 브라우저에 주소를 요청해서 받은 json 샘플 데이터로 Kotlin 데이터 클래스를 생성
1) 레트로핏 관련 파일만 모아둘 패키지를 생성
2) data 클래스 생성
- retrofit 패키지를 우클릭 - [New] - [Kotlin data class File from JSON] 을 클릭
- 복사한 JSON 데이터를 그대로 넣어줌. 클래스의 이름을 LibraryResponse 로 지정
- [Enable Inner Class Model]가 체크 해제된 것을 확인
3) 레트로핏 클래스 작성
- retrofit 패키지에 RetrofitConnection 클래스 생성
- 레트로핏 객체를 생성하는 getInstance() 메서드 생성
class RetrofitConnection {
// 객체를 하나만 생성하는 싱글턴 패턴을 적용.
companion object {
// API 서버의 주소가 BASE_URL이 돰.
private const val BASE_URL = "http://openapi.seoul.go.kr:8088/"
private var INSTANCE: Retrofit? = null
fun getInstance(): Retrofit {
if (INSTANCE == null) {
INSTANCE = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return INSTANCE!!
}
}
}
✓ 컨버퍼 팩토리는 서버에서 온 JSON 응답을 데이터 클래스 객체로 변환. Gson이라는 레트로핏 기본 컨버터 팩토리를 사용
4) HTTP 메서드를 정의해놓은 인터페이스 작성
🚀 HTTP 메서드를 작성해 레트로핏이 데이터를 가져올 수 있도록 작업.
🚀 인터페이스를 작성하면 레트로핏 라이브러리가 인터페이스에 정의된 API 엔드포인트들을 자동으로 구현
① 인터페이스를 추가
- LibraryService 인터페이스 생성
② 인터페이스를 작성
- LibraryService 인터페이스를 작성. 필요한 API는 GET 메서드.
interface LibraryService {
@GET("{apiKey}/json/SeoulPublicLibraryInfo/1/200/")
fun getLibrary(@Path("apiKey") key: String): Call<LibraryResponse>
}
5) 레트로핏으로 데이터 불러오기
🚀 작업한 인터페이스를 적용하고 데이터를 불러오는 코드를 작성
① MapActivity에 onMapReady() 아래에 loadLibrary() 메서드 작성
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val sydney = LatLng(-34.0, 151.0)
mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
loadLibrary()
}
private fun loadLibrary() {
}
② 정의한 인터페이스를 실행 가능한 객체로 변환
// 레트로핏 객체를 이용하면 LibraryService 인터페이스 구현체를 가져올 수 있음
val retrofitAPI = RetrofitConnection.getInstance().create(LibraryService::class.java)
③ 인터페이스에 정의된 getLibrary() 메서드에 api키를 입력하고, enqueue() 메서드를 호출해서 서버에 요청
private fun loadLibrary() {
val retrofitAPI = RetrofitConnection.getInstance().create(LibraryService::class.java)
retrofitAPI.getLibrary("API키").enqueue(object : Callback<LibraryResponse> {
override fun onResponse(call: Call<LibraryResponse>, response: Response<LibraryResponse>) {
TODO("Not yet implemented")
}
override fun onFailure(call: Call<LibraryResponse>, t: Throwable) {
TODO("Not yet implemented")
}
})
}
④ 오버라이드한 메서드를 구현
- 서버 요청이 실패했을 경우 간단한 토스트 메시지로 알려줌
- 서버에서 데이터를 정상적으로 받았다면 지도에 마커를 표시하는 메서드를 호출하도록 코드를 추가
private fun loadLibrary() {
// 레트로핏 객체를 이용하면 LibraryService 인터페이스 구현체를 가져올 수 있음.
val retrofitAPI = RetrofitConnection.getInstance().create(LibraryService::class.java)
retrofitAPI.getLibrary("4f666a5957736f6d35367479796e4e").enqueue(object: Callback<LibraryResponse> {
override fun onResponse(
call: Call<LibraryResponse>,
response: Response<LibraryResponse>) { // 지도에 마커 표시
showLibrary(response.body() as LibraryResponse)
}
override fun onFailure(call: Call<LibraryResponse>, t: Throwable) {
Toast.makeText(baseContext, "서버에서 데이터를 가져올 수 없습니다.", Toast.LENGTH_SHORT).show()
}
})
}
6) 지도에 도서관 마커 표시하기
① 지도에 마커를 표시하는 showLibrary() 메서드를 loadLibrary() 메서드 아래에 생성
private fun showLibrary(libraryResponse: LibraryResponse) { }
② 파라미터로 전달된 LibraryResponse의 SeoulPublicLibraryInfo.row에 도서관 목록이 담겨 있음
private fun showLibrary(libraryResponse: LibraryResponse) {
// 도서관 목록을 반복문으로 하나씩 꺼냄.
for (lib in libraryResponse.SeoulPublicLibraryInfo.row) {
// 마커의 좌표를 생성
val position = LatLng(lib.XCNTS.toDouble(), lib.YDNTS.toDouble())
// 좌표와 도서관 이름으로 마커를 생성
val marker = MarkerOptions().position(position).title(lib.LBRRY_NAME)
// 지도에 마커를 추가
mMap.addMarker(marker)
}
}
③ 지도를 보여주는 카메라가 시드니를 가르키므로 카메라의 위치 조정이 필요
- 수동으로 카메라의 좌표를 직접 입력해 주는 방법도 있지만 마커 전체의 영역을 구하고, 마커의 영역만큼 보여주는 코드를 작성
private fun showLibrary(libraryResponse: LibraryResponse) {
val latLngBounds = LatLngBounds.builder() // 마커 영역 지정
// 도서관 목록을 반복문으로 하나씩 꺼냄.
for (lib in libraryResponse.SeoulPublicLibraryInfo.row) {
val position = LatLng(lib.XCNTS.toDouble(), lib.YDNTS.toDouble())
val marker = MarkerOptions().position(position).title(lib.LBRRY_NAME)
mMap.addMarker(marker)
latLngBounds.include(marker.position) // 마커 추가
}
val bounds = latLngBounds.build() // 저장해 둔 마커의 영역을 구함
val padding = 0
// bounds와 padding으로 카메라를 업데이트
val updated = CameraUpdateFactory.newLatLngBounds(bounds, padding)
mMap.moveCamera(updated)
}
7) onMapReady에서 loadLibrary() 메서드 호출하기
🚀 onMapReady()에 기본으로 작성되어 있는 코드를 주석 처리하고 loadLibrary() 메서드를 호출
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
// val sydney = LatLng(-34.0, 151.0)
// mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
// mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
loadLibrary()
}
8) 도서관 클릭 시 홈페이지로 이동하기
🚀 클릭리스너로 새 창을 띄우거나 추가적인 처리를 할 수 있음. 여기서는 도서관 홈페이지의 url이 있는지 검사하고, 있으면 홈페이지를 웹 브라우저에 띄우는 코드를 작성
① 마커에 tag 정보를 추가
- 마커를 클릭하면 id와 같은 구분 값을 tag에 저장해두고 사용할 수 있음
- 지도에 마커를 추가하는 코드로 수정하고 tag 값에 홈페이지 주소를 저장
MapsActivity에서 showLibrary() 메서드의 다음 부분을 수정
for (lib in libraryResponse.SeoulPublicLibraryInfo.row) {
val position = LatLng(lib.XCNTS.toDouble(), lib.YDNTS.toDouble())
val marker = MarkerOptions().position(position).title(lib.LBRRY_NAME)
//mMap.addMarker(marker)
val obj = mMap.addMarker(marker)
obj?.tag = lib.HMPG_URL
latLngBounds.include(marker.position) // 마커 추가
}
② 클릭리스너를 달고 tag 홈페이지 주소를 웹 브라우저에 띄움
- onMapReady() 안에서 추가로 코드를 작성
- 지도에 마커클릭리스너를 달고 리스너를 통해 전달되는 마커의 tag를 검사해서 값이 있으면 인텐트로 홈페이지를 띄움
mMap.setOnMarkerClickListener {
if (it.tag != null) {
var url = it.tag as String
if (!url.startsWith("http")) {
url = "http://${url}"
}
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
}
true
}
[ 내용 참고 : IT 학원 강의 ]
'Android Studio' 카테고리의 다른 글
[Android Studio] 컨스트레인트 레이아웃 ConstraintLayout (0) | 2024.05.30 |
---|---|
[Android Studio] 맵 클러스터링 (0) | 2024.05.28 |
[Android Studio] 레트로핏 데이터 통신 라이브러리 (0) | 2024.05.26 |
[Android Studio] Room: ORM 라이브러리 (0) | 2024.05.23 |
[Android Studio] SQLite 예제 (2) 화면을 만들고 소스 코드 연결 (1) | 2024.05.23 |