'어따세워' 앱의 핵심 기능인 주차장 정보를 보여주기 위해서는 많은 양의 주차장 데이터가 필요하다.
그렇다면 이런 데이터들은 어떻게 얻을 수 있을까..?
바로 '공공데이터 포털'에서 제공받을 수 있다.
https://www.data.go.kr/index.do
공공 데이터 포털에는 주차장 데이터뿐만 아니라 다양한 데이터를 제공해주고 있으므로,
여러 데이터를 잘 활용하면 훌륭한 아이디어와 앱을 만들어낼 수 있다.
그럼 이제 본격적으로, 안드로이드에서 이러한 데이터들을 가져오는 방법을 살펴보도록 하자!
우선, 데이터를 사용하기 위해서는 '저는 이 데이터를 사용하겠습니다!' 라는 활용 신청 과정이 필요하다.
어려운 것은 아니고, 간단하게 활용 목적을 작성하고 제출하면 왠만한 데이터들은 바로 사용이 가능하다.
신청을 완료하면 위 사진처럼 '일반 인증키'를 제공해준다.
반드시 일반 인증키가 있어야 데이터를 가져올 수 있기에 절대 분실해서는 안되며, 노출 시 보안상 문제가 될 수 있으므로 가급적 private 하게 보관하는 것이 최선이다.
안드로이드에서 데이터를 보다 쉽게 가져올 수 있도록 제공해주는 Retrofit2 라이브러리가 있다.
이번 프로젝트를 진행하면서 Retrofit을 처음 사용해봤는데, 너무 간단하고 효율적이라 더욱 매료되었다.
Retrofit에 대해 처음 들어본 분들을 위해 짧게 설명하자면, Square사에서 제공해주는 REST API로, 서버와 클라이언트간의 Http 통신을 위한 인터페이스이다.
자세한 정보는 아래 링크에서 확인이 가능하다.
Retrofit - http://square.github.io/retrofit
dependencies {
...
// Retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
...
}
우선 build.gradle(app level)의 dependencies 블록에 위 라이브러리들을 작성해준다.
여기서 gson은 retrofit을 통해 받아온 데이터를 json 형태로 변환할 수 있도록 도와주는 라이브러리이다.
위 코드를 작성하였다면, 이제 공공데이터에서 받아올 데이터를 담을 DTO(Data Transfer Object)를 생성해준다.
(코드가 너무 길기때문에 글의 흐름을 망칠 수 있으므로 접어놓았다.)
(더보기 클릭)
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
data class ParkingLot(
@Expose
@SerializedName("response")
val response: Response
)
data class Response(
@Expose
@SerializedName("header")
val header: Header,
@Expose
@SerializedName("body")
val body: Body
)
data class Body(
@Expose
@SerializedName("items")
val items: List<Items>,
@Expose
@SerializedName("totalCount")
val totalcount: String,
@Expose
@SerializedName("numOfRows")
val numofrows: String,
@Expose
@SerializedName("pageNo")
val pageno: String
)
data class Items(
@Expose
@SerializedName("prkplceNo")
val prkplceno: String,
@Expose
@SerializedName("prkplceNm")
val prkplcenm: String,
@Expose
@SerializedName("prkplceSe")
val prkplcese: String,
@Expose
@SerializedName("prkplceType")
val prkplcetype: String,
@Expose
@SerializedName("rdnmadr")
val rdnmadr: String,
@Expose
@SerializedName("lnmadr")
val lnmadr: String,
@Expose
@SerializedName("prkcmprt")
val prkcmprt: String,
@Expose
@SerializedName("feedingSe")
val feedingse: String,
@Expose
@SerializedName("enforceSe")
val enforcese: String,
@Expose
@SerializedName("operDay")
val operday: String,
@Expose
@SerializedName("weekdayOperOpenHhmm")
val weekdayoperopenhhmm: String,
@Expose
@SerializedName("weekdayOperColseHhmm")
val weekdayopercolsehhmm: String,
@Expose
@SerializedName("satOperOperOpenHhmm")
val satoperoperopenhhmm: String,
@Expose
@SerializedName("satOperCloseHhmm")
val satoperclosehhmm: String,
@Expose
@SerializedName("holidayOperOpenHhmm")
val holidayoperopenhhmm: String,
@Expose
@SerializedName("holidayCloseOpenHhmm")
val holidaycloseopenhhmm: String,
@Expose
@SerializedName("parkingchrgeInfo")
val parkingchrgeinfo: String,
@Expose
@SerializedName("basicTime")
val basictime: String,
@Expose
@SerializedName("basicCharge")
val basiccharge: String,
@Expose
@SerializedName("addUnitTime")
val addunittime: String,
@Expose
@SerializedName("addUnitCharge")
val addunitcharge: String,
@Expose
@SerializedName("dayCmmtktAdjTime")
val daycmmtktadjtime: String,
@Expose
@SerializedName("dayCmmtkt")
val daycmmtkt: String,
@Expose
@SerializedName("monthCmmtkt")
val monthcmmtkt: String,
@Expose
@SerializedName("metpay")
val metpay: String,
@Expose
@SerializedName("spcmnt")
val spcmnt: String,
@Expose
@SerializedName("institutionNm")
val institutionnm: String,
@Expose
@SerializedName("phoneNumber")
val phonenumber: String,
@Expose
@SerializedName("latitude")
val latitude: String,
@Expose
@SerializedName("longitude")
val longitude: String,
@Expose
@SerializedName("referenceDate")
val referencedate: String,
@Expose
@SerializedName("insttCode")
val insttcode: String
)
data class Header(
@Expose
@SerializedName("resultCode")
val resultcode: String,
@Expose
@SerializedName("resultMsg")
val resultmsg: String,
@Expose
@SerializedName("type")
val type: String
)
이렇게 ParkingLot DTO를 작성했다면, 이젠 본격 Retrofit을 사용할 시간이다.
interface ParkingLotAPI {
@GET("/openapi/tn_pubr_prkplce_info_api")
fun getParkingLot(
@Query("serviceKey") apiKey: String,
@Query("pageNo") pageNo: Int = 0,
@Query("numOfRows") numOfRows: Int = 100,
@Query("type") type: String = "json"
): Call<ParkingLot>
}
이는 class가 아닌 interface로 생성해주어야 하며, 공공 데이터 포털에서 데이터를 가져와야 하는 상황이기 때문에 GET방식을 사용하였다.
@GET 안에는 base url 이후에 나올 uri를 추가로 작성해주면 된다.
그리고 @Query에는 api에서 요청하는 Request Parameter를 작성해주면 된다.
open class BaseRetrofitBuilder {
open val baseUrl = "본인이 사용할 Base Url"
private val gson = GsonBuilder()
.setLenient()
.create()
private val clientBuilder = OkHttpClient.Builder().addInterceptor(
HttpLoggingInterceptor().apply {
// body 로그를 출력하기 위한 interceptor
level = HttpLoggingInterceptor.Level.BODY
}
)
private val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(clientBuilder.build())
.build()
protected fun getRetrofit(): Retrofit {
return retrofit
}
}
본인은 이번 프로젝트에서 액티비티, 프래그먼트, 뷰모델 모두 Base Class를 만들어 유지보수성을 높이는 훈련을 하고 있다.
따라서 RetrofitBuilder 클래스도 Base로 만들어 다른 상황에서도 쉽게 사용할 수 있도록 하였다.
우선 baseUrl 변수에는 본인이 사용할 base url을 입력해주면 된다.
공공 데이터 포털에서는 데이터를 제공해주는 url이 적혀있기 때문에 참고해서 작성하기 바란다.
또한, converter로 gson을 지정해주고, 요청한 값이 body부분의 로그를 출력하기 위해서 interceptor까지 적용해주었다.
object RetrofitParkingAPIBuilder : BaseRetrofitBuilder() {
// base url을 공공 데이터 포털 사이트로 지정
override val baseUrl: String
get() = "http://api.data.go.kr"
const val API_KEY = "공공 데이터 포털에서 제공받은 API KEY"
fun getParkingLots() {
val api = getRetrofit().create(ParkingLotAPI::class.java)
api.getParkingLot(API_KEY).enqueue(object : Callback<ParkingLot> {
override fun onResponse(call: Call<ParkingLot>, response: Response<ParkingLot>) {
val resultCode = response.body()?.response?.header?.resultcode
val resultMessage = response.body()?.response?.header?.resultmsg
// result code가 00이면 정상적으로 데이터를 가져옴
if(resultCode == "00") {
response.body()?.response?.body?.items?.forEach { data ->
// 주차장 이름 출력
Timber.d("주차장 명 : ${data.prkplcenm}")
}
} else { // 데이터를 정상적으로 가져오지 못했을 때
Timber.d("에러 발생 : ${resultCode.toString()}")
Timber.d("에러 발생 : ${resultMessage.toString()}")
}
}
override fun onFailure(call: Call<ParkingLot>, t: Throwable) {
Timber.e("데이터 불러오기 실패 : ${t.message}")
}
})
}
}
API_KEY 변수란에 정확한 키값을 입력해주지 않으면 오류가 나기때문에 반드시 확실한 복붙이 필요하다..!! 😂
retrofit 객체의 create() 메서드 안에는 우리가 아까 만들어둔 ParkingLotAPI 인터페이스를 입력해준다.
그렇게 만든 api의 getParkingLot() 메서드에 API_KEY를 전달해주고 enqueue에 콜백과 함께 코드를 작성해주면 끝이다.
공공 데이터 포털에서 result code를 확인한 결과 "00" 일 경우에만 정상적으로 데이터를 가져온 것이므로, 데이터의 주차장 이름만 가져와서 로그를 찍어보았다.
그렇지 않은 경우는 에러가 발생한 상황이므로 예외 처리를 해주면 된다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 주차장 데이터 가져오기
RetrofitParkingAPIBuilder.getParkingLots()
...
}
이제 Activity에서 테스트로 getParkingLots() 메서드를 실행해보자!
다행히 정상적으로 데이터를 모두 가져온 것을 확인할 수 있었다.
또한, interceptor에서 Body단의 내용을 출력하도록 지정해두었기 때문에 위 사진처럼 정상적으로 로그가 출력되는 모습까지 확인하였다.
만약 문제가 있다면 200 OK라고 나오지 않고 다른 메세지가 출력될 것이다.
이렇게 성공적으로 데이터를 가져왔지만, 프로젝트에서 이 데이터를 활용하는 데에 있어 가장 큰 문제가 따로 있었으니..
이 내용은 다음 포스팅에서 다루도록 하겠다..!!
'Project > 어따세워' 카테고리의 다른 글
[Android][어따세워] 앱 아이콘 제작 / 해상도별 사이즈 조절(포토샵) (0) | 2021.12.09 |
---|---|
[Android][어따세워] [1] Android MVVM 회원가입&로그인 - 파이어베이스를 사용하여 계정을 등록해보자! (0) | 2021.12.05 |
[Android][어따세워] 메타버스 스터디룸(게더타운) 개설! (0) | 2021.11.30 |
[Android][어따세워] 토이 프로젝트 주제 정하기 (6) | 2021.11.29 |