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"

+ Recent posts