-
[Android] 미세먼지 앱 (공공 데이터 API) + Retrofit안드로이드 2024. 5. 1. 15:35
✏️ TIL(Today I Learned)
도시를 선택하면 그에 해당하는 데이터를 지역 선택 스피너에 받아와서 선택을 할 수 있다.
선택이 완료되면, TextView에 지역명이 표기되면서 미세먼지 농도와 상태가 이모티콘, 텍스트, 배경색으로 표시된다.
API 사용하기 위해 공공데이터포털로 들어가서, 활용신청을 한다.
그럼 바로 승인이 된 것을 확인할 수 있다.
이를 클릭하여, 개발계정 상세 보기로 들어간다.
그리고 미리보기를 클릭하여 데이터의 json을 확인해 볼 수 있다.
아래와 같은 형식이다.
{ "response": { "body": { "totalCount": 0, "items": [], "pageNo": 1, "numOfRows": 100 }, "header": { "resultMsg": "NORMAL_CODE", "resultCode": "00" } } }
💻 코드
개발하기에 앞서, http통신을 위한 인터넷 사용권한을 AndroidManifest.xml에 추가한다.
그리고 <application> 태그에 android:networkSecurityConfig 속성을 추가한다.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET"/> <application android:networkSecurityConfig="@xml/network_security_config"
앱의 네트워크 보안 구성을 수정하여 apis.data.go.kr에 대해 평문 트래픽을 특별히 허용하기 위함이다.
(최신 안드로이드 버전은 기본적으로 보안상의 이유로 평문 트래픽을 허용하지 않는다.)
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">apis.data.go.kr</domain> </domain-config> </network-security-config>
그 다음 위에서 확인한 json을 data class형태로 바꾸기 위해, 이에 맞는 DTO를 만들어준다.
변수명은 원래 서버에서 사용하는 값과 똑같이 작성해야 된다.
data class Dust(val response: DustResponse) data class DustResponse( @SerializedName("body") val dustBody: DustBody, @SerializedName("header") val dustHeader: DustHeader ) data class DustBody( val totalCount: Int, @SerializedName("items") val dustItem: MutableList<DustItem>?, val pageNo: Int, val numOfRows: Int ) data class DustHeader( val resultCode: String, val resultMsg: String ) data class DustItem( val so2Grade: String, val coFlag: String?, val khaiValue: String, val so2Value: String, val coValue: String, val pm25Flag: String?, val pm10Flag: String?, val o3Grade: String, val pm10Value: String, val khaiGrade: String, val pm25Value: String, val sidoName: String, val no2Flag: String?, val no2Grade: String, val o3Flag: String?, val pm25Grade: String, val so2Flag: String?, val dataTime: String, val coGrade: String, val no2Value: String, val stationName: String, val pm10Grade: String, val o3Value: String )
시도명을 검색조건으로 할 것이므로 아래의 사진을 참고한다.
Retrofit 네트워크 인터페이스를 통해, 시도별 실시간 측정 정보를 얻어온다.
코루틴(Coroutine)을 사용하여 비동기적으로 API를 호출한다.
interface NetWorkInterface { @GET("getCtprvnRltmMesureDnsty") //시도별 실시간 측정 정보 조회 주소 suspend fun getDust(@QueryMap param: HashMap<String, String>): Dust }
그다음, NetWorkClient 객체를 만들어 Retrofit 라이브러리를 사용하여,
네트워크 요청을 구성하고 실행하는 데 필요한 구성 요소들을 정의한다.
createOkHttpClient()는 네트워크 요청을 수행할 때 사용될 OkHttpClient 인스턴스를 생성한다.
- HttpLoggingInterceptor는 로깅 인터셉터를 사용하여 요청과 응답에 대한 로그를 제공한다.
앱이 디버그 모드인 경우 요청의 바디 내용을 모두 로깅하고, 아닐 경우 로깅하지 않는다. - 타임아웃 설정하여, 서버로부터 응답을 기다리는 최대 시간을 정의한다.
dustRetrofit는 Retrofit.Builder()를 사용하여 Retrofit 인스턴스를 생성한다. 이는 API 호출을 수행하는 데 사용된다.
- baseUrl(DUST_BASE_URL): 모든 네트워크 요청의 기본 URL로 설정한다.
- addConverterFactory(GsonConverterFactory.create()): Gson 컨버터 팩토리를 추가하여 JSON 응답을 Kotlin 객체로 자동 변환할 수 있다.
- client(createOkHttpClient()): 위에서 정의한 OkHttpClient 인스턴스를 HTTP 클라이언트로 사용한다.
dustNetWork는 NetWorkInterface 인터페이스를 구현하는 객체를 생성한다.
이 객체는 Retrofit이 제공하는 동적 프록시 기능을 통해 생성되며, 이 인터페이스에 정의된 메서드를 호출하면 자동으로 HTTP 요청이 구성되고 실행된다.object NetWorkClient { private const val DUST_BASE_URL = "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/" private fun createOkHttpClient(): OkHttpClient { val interceptor = HttpLoggingInterceptor() if (BuildConfig.DEBUG) interceptor.level = HttpLoggingInterceptor.Level.BODY else interceptor.level = HttpLoggingInterceptor.Level.NONE return OkHttpClient.Builder() .connectTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) .writeTimeout(20, TimeUnit.SECONDS) .addNetworkInterceptor(interceptor) .build() } private val dustRetrofit = Retrofit.Builder() .baseUrl(DUST_BASE_URL).addConverterFactory(GsonConverterFactory.create()).client( createOkHttpClient() ).build() val dustNetWork: NetWorkInterface = dustRetrofit.create(NetWorkInterface::class.java) }
마지막으로, MainActivity에서 미세먼지 데이터를 받아와서 화면에 표시한다.
class MainActivity : AppCompatActivity() { private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } var items = mutableListOf<DustItem>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) binding.spinnerViewSido.setOnSpinnerItemSelectedListener<String> { _, _, _, text -> communicateNetWork(setUpDustParameter(text)) } binding.spinnerViewGoo.setOnSpinnerItemSelectedListener<String> { _, _, _, text -> var selectedItem = items.filter { f -> f.stationName == text } binding.tvCityname.text = selectedItem[0].sidoName + " " + selectedItem[0].stationName binding.tvDate.text = selectedItem[0].dataTime binding.tvP10value.text = selectedItem[0].pm10Value + " ㎍/㎥" when (getGrade(selectedItem[0].pm10Value)) { 1 -> { binding.mainBg.setBackgroundColor(Color.parseColor("#9ED2EC")) binding.ivFace.setImageResource(R.drawable.image_mise1) binding.tvP10grade.text = "좋음" } 2 -> { binding.mainBg.setBackgroundColor(Color.parseColor("#D6A478")) binding.ivFace.setImageResource(R.drawable.image_mise2) binding.tvP10grade.text = "보통" } 3 -> { binding.mainBg.setBackgroundColor(Color.parseColor("#DF7766")) binding.ivFace.setImageResource(R.drawable.image_mise3) binding.tvP10grade.text = "나쁨" } 4 -> { binding.mainBg.setBackgroundColor(Color.parseColor("#BB3320")) binding.ivFace.setImageResource(R.drawable.image_mise4) binding.tvP10grade.text = "매우나쁨" } } } } private fun communicateNetWork(param: HashMap<String, String>) = lifecycleScope.launch() { val responseData = NetWorkClient.dustNetWork.getDust(param) items = responseData.response.dustBody.dustItem!! val goo = ArrayList<String>() items.forEach { Log.d("add Item :", it.stationName) goo.add(it.stationName) } runOnUiThread { binding.spinnerViewGoo.setItems(goo) } } private fun setUpDustParameter(sido: String): HashMap<String, String> { val authKey = getString(R.string.key) return hashMapOf( "serviceKey" to authKey, "returnType" to "json", "numOfRows" to "100", "pageNo" to "1", "sidoName" to sido, "ver" to "1.0" ) } fun getGrade(value: String): Int { val mValue = value.toInt() var grade = 1 grade = if (mValue >= 0 && mValue <= 30) { 1 } else if (mValue >= 31 && mValue <= 80) { 2 } else if (mValue >= 81 && mValue <= 100) { 3 } else 4 return grade } }
'안드로이드' 카테고리의 다른 글
- HttpLoggingInterceptor는 로깅 인터셉터를 사용하여 요청과 응답에 대한 로그를 제공한다.