
키워드 : RecyclerView, Retrofit, Spinner, API, DTO
API
API는 OpenWeatherAPI를 사용
3시간 간격 4일 (Forecast), 실시간 날씨 (Current)를 사용하였습니다.
Weather API - OpenWeatherMap
Please, sign up to use our fast and easy-to-work weather APIs. As a start to use OpenWeather products, we recommend our One Call API 3.0. For more functionality, please consider our products, which are included in professional collections.
openweathermap.org
Hourly Forecast 4 Days와 Current Weather Data 살펴보기
필수 파라미터 값
q(도시)
appid(api값)
Hourly Forecast 같은 경우 "list" 배열 내부에
main - temp - 온도
weather - icon - 아이콘
dt_txt - 시간
온도, 아이콘, 시간만으로 기상예보 (3시간, 4일)을 출력하겠습니다.
ForecastDTO (기상예보 뽑아올 데이터)
data class ForcecastDTO(
//list는 배열이기 때문에 List<>로 선언 후
//list 내부에 있는 온도, 아이콘, 시간을 추출합니다
@SerializedName("list") val list: List<ForcecastMain>
)
data class ForcecastMain(
//val main, time은 객체 타입이기 때문에 Class로
//val weather은 배열이기 때문에 List<>로 지정합니다.
@SerializedName("main") val main: ForcecasTemp,
@SerializedName("weather") val weather: List<ForcecasWeather>,
@SerializedName("dt_txt")val time: String
)
data class ForcecasTemp(
//temp - 온도
val temp: Double
)
data class ForcecasWeather(
//icon - 아이콘을 통해 openWeather API에서 제공하는
//아이콘으로 기상예보를 표시합니다.
val icon: String
)
ResultDTO (실시간 날씨 뽑아올 데이터)
data class RealtimeDTO (
val weather: List<Weather>,
val main: Main,
val wind: Wind
)
data class Weather(
val main: String?,
val description: String?,
val icon: String?
)
data class Main(
val temp: Double?,
val humidity: Int?,
val temp_min: Double?,
val temp_max: Double?
)
data class Wind(
val speed: Double?,
)
WeatherAPI interface (필수 파라미터)
interface WeatherAPI {
@GET("data/2.5/weather")
fun getRealtime(
@Query("q") q: String,
@Query("appid") appid: String
): Call<RealtimeDTO>
//q : 도시명, appid : API키 모두 필수 파라미터입니다.
@GET("data/2.5/forecast")
fun getForecast(
@Query("q") q: String,
@Query("appid") appid: String,
@Query("lang") lang: String
) : Call<ForcecastDTO>
}
베이스 url https://api.openweathermap.org
을 제외한 나머지를 @GET형식으로 불러옵니다.
GET 방식은 URL에 request 파라미터를 추가하는 방식입니다.
WeatherActivity.kt
class WeatherActivity : AppCompatActivity() {
private lateinit var binding: ActivityWeatherBinding
private val HOST_URL = "https://api.openweathermap.org/"
private var cityName = "Seoul"
private val API_KEY = "API키"
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWeatherBinding.inflate(layoutInflater)
setContentView(binding.root)
locationinit() //Spinner 도시 설정
getRealtime() //실시간 날씨 가져오기
getForecast() //3시간 기준 4일 날씨
timeinit() //실시간 시간 가져오기
refreshButtoninit() //날씨 새로고침
}
//RecyclerView에 필요한 adapter 장착 및 가로 RecyclerView
private fun setAdapter(dataList : ArrayList<ForcecastMain>) {
val myAdapter = WeatherAdapter(this, dataList)
binding.forecastRecyclerView.adapter = myAdapter
binding.forecastRecyclerView.layoutManager = LinearLayoutManager(this@WeatherActivity, LinearLayoutManager.HORIZONTAL, false)
binding.forecastRecyclerView.setHasFixedSize(false)
}
//DropDown Spinner로 지역을 선택하게 하고 선택과 동시에
//getRealtime(), timeinit(), getForecast()를 호출합니다.
private fun locationinit() = with(binding) {
val cityList = listOf("서울", "인천", "춘천", "강릉", "대전", "충청북도", "충청남도", "대구", "전주", "목포", "광주", "부산", "제주")
val adapter = ArrayAdapter(this@WeatherActivity, R.layout.style_spinner, cityList)
adapter.setDropDownViewResource(R.layout.style_spinner2)
spinnerLocation.adapter = adapter
spinnerLocation.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
@RequiresApi(Build.VERSION_CODES.O)
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when(position) {
0 -> {
cityName = "Seoul"
getRealtime()
timeinit()
getForecast()
}
1 -> {
cityName = "Incheon"
getRealtime()
timeinit()
getForecast()
}
... 생략
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
}
//실시간 날씨예보 (온도, 습도, 풍속 등)
private fun getRealtime() = with(binding) {
//retrofit Builder패턴 생성 및 url 지정, Gson Data 변환
val retrofit = Retrofit.Builder()
.baseUrl(HOST_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
//interface 지정 후 DTO를 통해 원하는 값 불러오기
val weatherAPI = retrofit.create(WeatherAPI::class.java)
val call = weatherAPI.getRealtime(cityName, API_KEY)
call.enqueue(object : Callback<RealtimeDTO> {
@SuppressLint("SetTextI18n")
override fun onResponse(call: Call<RealtimeDTO>, response: Response<RealtimeDTO>) {
if(response.isSuccessful) {
//현재 온도, 최고온도, 최저 온도,
val now = (String.format("%.1f", response.body()!!.main.temp!! - 273))
val max = (String.format("%.1f",response.body()!!.main.temp_max!! - 273)) + "°C"
val min = (String.format("%.1f",response.body()!!.main.temp_min!! - 273)) + "°C"
temperatureTextView.text = now
maxvalueTextView.text = max
minvalueTextView.text = min
//습도 및 풍속
humidityTextView.text = response.body()!!.main.humidity.toString() + "%"
windspeedTextView.text = response.body()!!.wind.speed.toString() + "km/h"
}
}
//실패 시 예외처리
override fun onFailure(call: Call<RealtimeDTO>, t: Throwable) {
Log.d("결과3", "실패! $t")
}
})
}
//3시간 기준 4일 일기예보
private fun getForecast() {
val retrofit = Retrofit.Builder()
.baseUrl(HOST_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val weatherAPI = retrofit.create(WeatherAPI::class.java)
val call = weatherAPI.getForecast(cityName, API_KEY, "kr")
call.enqueue(object: Callback<ForcecastDTO> {
override fun onResponse(call: Call<ForcecastDTO>, response: Response<ForcecastDTO>) {
if(response.isSuccessful) {
val body = response.body()
body?.let {
setAdapter(it.list as ArrayList<ForcecastMain>)
}
}
}
override fun onFailure(call: Call<ForcecastDTO>, t: Throwable) {
Log.d("포케스트 결과", "실패")
}
})
}
//실시간 시간을 불러오고, 시간에 따라 BackGround 변경
@SuppressLint("SetTextI18n")
@RequiresApi(Build.VERSION_CODES.O)
private fun timeinit() = with(binding) {
val today = LocalDate.now().toString()
todayTextView.text = today
val hour = LocalDateTime.now().hour.toString()
val minute = LocalDateTime.now().minute.toString()
nowTimeTextView.text = hour + ":" + minute
if(hour.toInt() >= 12) {
nowTimeTextView.text = "오후 " + hour + ":" + minute
} else {
nowTimeTextView.text = "오전 " + hour + ":" + minute
}
if(18 <= hour.toInt() || 0 == hour.toInt() || hour.toInt() <= 6) {
backGround.setBackgroundResource(R.drawable.nightbackground)
view1.setBackgroundResource(R.drawable.shape2)
view2.setBackgroundResource(R.drawable.shape2)
weatherImageView.setBackgroundResource(R.drawable.ic_night)
} else {
backGround.setBackgroundResource(R.color.moring)
view1.setBackgroundResource(R.drawable.shape1)
view2.setBackgroundResource(R.drawable.shape1)
weatherImageView.setBackgroundResource(R.drawable.ic_afternoon)
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun refreshButtoninit() = with(binding) {
refreshButton.setOnClickListener {
progressBar.visibility = View.VISIBLE
this@WeatherActivity.timeinit()
getRealtime()
progressBar.visibility = View.GONE
Toast.makeText(this@WeatherActivity, "날씨가 갱신되었습니다.", Toast.LENGTH_SHORT).show()
}
}
}
WeatherAdapter.kt
class WeatherAdapter(private val context: Context, private val datas: ArrayList<ForcecastMain>): RecyclerView.Adapter<WeatherAdapter.ViewHolder>() {
//WeatherAdapter와 item_list를 연결
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return WeatherAdapter.ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_forecast_list, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
//데이터를 순서대로 모든 값 바인딩
holder.bind(datas[position], context)
}
override fun getItemCount() = datas.size //어탭터로 바인딩된 아이템 개수 반환
//item_list에 있는 값과 Retrofit으로 불러온 값을 연결
class ViewHolder(view: View?): RecyclerView.ViewHolder(view!!) {
private val iconImage = view?.findViewById<ImageView>(R.id.iconImageView)
private val realtime = view?.findViewById<TextView>(R.id.timeTextView)
private val temp = view?.findViewById<TextView>(R.id.tempTextView)
fun bind(data: ForcecastMain, context: Context){
data.weather.forEach {
val icon = it.icon
val iconUrl = "http://openweathermap.org/img/w/" + icon + ".png"
//Glide 이미지라이브러리를 통해 아이콘 불러옴
Glide.with(context).load(iconUrl).into(iconImage!!)
}
temp?.text = (String.format("%.1f", data.main.temp - 273)) + "°C"
val str = data.time
val subString = str.substring(5 until 10)
val subString2 = str.substring(11 until 16)
realtime?.text = subString + "\n" + subString2
}
}
}
'Android-Kotlin📱' 카테고리의 다른 글
[Android] Constraint Layout Guideline (컨스트레인트 레이아웃 가이드라인) (0) | 2022.06.07 |
---|---|
[Kotlin] Delegation Pattern, by (0) | 2022.06.01 |
[Android/Kotlin] Naver Geocoding 주소로 위도 경도 추출 코틀린 json 데이터 파싱 (0) | 2022.05.24 |
[Android/Kotlin] 실시간 위치 구현하기 with Naver map SDK (0) | 2022.05.23 |
[Android/Kotlin] Naver map SDK 이용한 지도 구현하기 (0) | 2022.05.23 |