728x90
gson - JSON 변환
Retrofit - HTTP Api 통신
okhttp - 로그 확인용
spninner - 스피너 라이브러리
DustData
import com.google.gson.annotations.SerializedName
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
)
구조
response 안에 body, header
body 안에 totalCount, items, pageNo, numOfRows
hearder 안에 resultCode, resultMsg
변수와 해당 json 데이터 형식이 맞아야 하는데, @SerializedName("body") 어노테이션을 사용해서 연결시켜 줌
NetWorkClient
package com.example.miseya.Retrofit
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object NetWorkClient {
//https 로 변경
private const val BASE_URL = "https://apis.data.go.kr/B552584/ArpltnInforInqireSvc/"
val dustNetWork :NetWorkInterface by lazy { dustRetrofit.create(NetWorkInterface::class.java) }
private val dustRetrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}).build())//client 개발때 만 사용 배포시 삭제 -> 로그확인용
.build()
}
방법 2
// 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(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().apply {
// level = HttpLoggingInterceptor.Level.BODY
// }).build())
// .build()
//
// val dustNetWork: NetWorkInterface = dustRetrofit.create(NetWorkInterface::class.java)
}
NetWorkInterface
package com.example.miseya.Retrofit
import com.example.miseya.data.Dust
import retrofit2.http.GET
import retrofit2.http.Query
private const val apiKey =
"발급받은 키"
private const val SidoName = "서울"
interface NetWorkInterface {
@GET("getCtprvnRltmMesureDnsty")
suspend fun getDust(
@Query("serviceKey") serviceKey: String = apiKey,
@Query("returnType") returnType: String = "json",
@Query("numOfRows") numOfRows: String = "50",
@Query("pageNo") pageNo: String = "1",
@Query("sidoName") sidoName: String = SidoName,
@Query("ver") ver: String = "1.0",
): Dust
}
방법 2
//interface NetWorkInterface {
// @GET("getCtprvnRltmMesureDnsty") //시도별 실시간 측정정보 조회 주소
// suspend fun getDust(@QueryMap param: HashMap<String, String>): Dust
//}
MainActivity
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.miseya.Retrofit.NetWorkClient
import com.example.miseya.data.DustItem
import com.example.miseya.databinding.ActivityMainBinding
import com.skydoves.powerspinner.IconSpinnerAdapter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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)
with(binding) {
spinnerViewSido.setOnSpinnerItemSelectedListener<String> { _, _, _, text ->
//communicateNetWork(setUpDustParameter(text))
communicateNetWork(text)
}
spinnerViewGoo.setOnSpinnerItemSelectedListener<String> { _, _, _, text ->
var selectedItem = items.filter { f -> f.stationName == text }
tvCityname.text = selectedItem[0].sidoName + " " + selectedItem[0].stationName
tvDate.text = selectedItem[0].dataTime
tvP10value.text = selectedItem[0].pm10Value + " ㎍/㎥"
when (getGrade(selectedItem[0].pm10Value)) {
1 -> {
mainBg.setBackgroundColor(Color.parseColor("#9ED2EC"))
ivFace.setImageResource(R.drawable.mise1)
tvP10grade.text = "좋음"
}
2 -> {
mainBg.setBackgroundColor(Color.parseColor("#D6A478"))
ivFace.setImageResource(R.drawable.mise2)
tvP10grade.text = "보통"
}
3 -> {
mainBg.setBackgroundColor(Color.parseColor("#DF7766"))
ivFace.setImageResource(R.drawable.mise3)
tvP10grade.text = "나쁨"
}
4 -> {
mainBg.setBackgroundColor(Color.parseColor("#BB3320"))
ivFace.setImageResource(R.drawable.mise4)
tvP10grade.text = "매우나쁨"
}
}
}
}
}
private fun communicateNetWork(sido: String) = CoroutineScope(Dispatchers.IO).launch {
//GET 응답 데이터
val resourceData = NetWorkClient.dustNetWork.getDust(sidoName = sido)
items = resourceData.response.dustBody.dustItem!!
//선택한 해당 시/도 의 측정 위치 stationName(문서참조)를 추가
val goo = ArrayList<String>()
items.forEach {
goo.add(it.stationName)
}
withContext(Dispatchers.Main) {
//Api 를 받아오는 작업을 코루틴에서 비동기로 처리 -> Dispatchers.IO
//UI를 변경 하기 위해 withContext Dispatchers.Main으로 스위칭(UI작업은 메인스레드에서만)
binding.spinnerViewGoo.setItems(goo)
val adapter = IconSpinnerAdapter(binding.spinnerViewGoo)
}
}
방법 2
// private fun communicateNetWork(param: HashMap<String, String>) = lifecycleScope.launch() {
// val responseData = NetWorkClient.dustNetWork.getDust(param)
//
// val adapter = IconSpinnerAdapter(binding.spinnerViewGoo)
// items = responseData.response.dustBody.dustItem!!
//
// val goo = ArrayList<String>()
// items.forEach {
// goo.add(it.stationName)
// }
//
// runOnUiThread {
// binding.spinnerViewGoo.setItems(goo)
// }
//
// }
// private fun setUpDustParameter(sido: String): HashMap<String, String> {
// val authKey =
// "발급받은 키"
//
// 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
}
}
주석 처리부분 -> @QueryMap, HashMap으로 응답 데이터를 받는 방법
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#9ED2EC"
tools:context=".MainActivity">
<com.skydoves.powerspinner.PowerSpinnerView
android:id="@+id/spinnerView_sido"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/md_green_200"
android:foreground="?attr/selectableItemBackground"
android:gravity="center"
android:hint="도시 선택"
android:padding="10dp"
android:textColor="@color/white_93"
android:textColorHint="@color/white_70"
android:textSize="14.5sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/spinnerView_goo"
app:spinner_arrow_gravity="end"
app:spinner_arrow_tint="@color/yellow"
app:spinner_divider_color="@color/white_70"
app:spinner_divider_show="true"
app:spinner_divider_size="0.4dp"
app:spinner_item_array="@array/Sido"
app:spinner_item_height="46dp"
app:spinner_popup_animation="normal"
app:spinner_popup_background="@color/background800"
app:spinner_popup_elevation="14dp"
tools:ignore="HardcodedText,UnusedAttribute" />
<com.skydoves.powerspinner.PowerSpinnerView
android:id="@+id/spinnerView_goo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/md_green_100"
android:foreground="?attr/selectableItemBackground"
android:gravity="center"
android:hint="지역 선택"
android:padding="10dp"
android:textColor="@color/white_93"
android:textColorHint="@color/white_70"
android:textSize="14.5sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/spinnerView_sido"
app:layout_constraintTop_toTopOf="parent"
app:spinner_arrow_gravity="end"
app:spinner_arrow_tint="@color/yellow"
app:spinner_divider_color="@color/white_70"
app:spinner_divider_show="true"
app:spinner_divider_size="0.4dp"
app:spinner_item_height="46dp"
app:spinner_popup_animation="normal"
app:spinner_popup_background="@color/background800"
app:spinner_popup_elevation="14dp"
tools:ignore="HardcodedText,UnusedAttribute" />
<ImageView
android:id="@+id/iv_face"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/mise1" />
<TextView
android:id="@+id/tv_p10value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text=" - ㎍/㎥"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_face" />
<TextView
android:id="@+id/tv_p10grade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text=""
android:textColor="#048578"
android:textSize="30sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_p10value" />
<TextView
android:id="@+id/tv_cityname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="도시를 선택해 주세요."
android:textColor="#242323"
android:textSize="36sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/iv_face"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:text=""
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_cityname" />
</androidx.constraintlayout.widget.ConstraintLayout>
Manifest에 인터넷 권한 추가
<uses-permission android:name="android.permission.INTERNET"/>
EnPoint(BaseUrl) 이 https 가 아닌경우 android:usesCleartextTraffic="true" 추가해줌
android:usesCleartextTraffic="true"