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 학원 강의 ]

+ Recent posts