날씨 정보 불러오기 (OpenWeathermap API)
Fragment 화면에 현 위치의 날씨 정보를 띄워보자!
xml
먼저 날씨 정보를 표시할 레이아웃은 다음과 같다. 날씨에 해당하는 아이콘 weather_ic
, 현재 기온을 표시할 temperature_tv
, 현재 날씨(ex. rain)를 표시할 weather_tv
가 있다.
<RelativeLayout
android:id="@+id/weather_tip_container"
android:padding="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/weather_ic"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:layout_margin="@dimen/margin_8"
android:src="@drawable/clear" />
<LinearLayout
android:id="@+id/weather_txt_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="@dimen/margin_8"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/weather_ic">
<TextView
android:id="@+id/temperature_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
tools:text="00" />
<TextView
android:id="@+id/weather_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Sunny"/>
</LinearLayout>
</RelativeLayout>
AndroidManifest - permission
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
인터넷과 위치 정보를 가져오기 위한 권한을 추가한다
build.gradle
implementation 'com.loopj.android:android-async-http:1.4.11'
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
( + 나는 More than one file was found with OS independent path 'META-INF/LICENSE.md'
이런식의 에러가 나서 packagingOptions
도 추가했다. 참고 stackoverflow )
OpenWeather 가입
API를 사용해 날씨 정보를 불러오려면 OpenWeather 사이트에 가입해 개인 API KEY
를 받아야 한다.
Fragment 전체
class StartFragment : Fragment() {
companion object {
const val API_KEY: String = "@%$@%@$!^*%@$@#!$$%"
const val WEATHER_URL: String = "https://api.openweathermap.org/data/2.5/weather"
const val MIN_TIME: Long = 5000
const val MIN_DISTANCE: Float = 1000F
const val WEATHER_REQUEST: Int = 102
}
private var binding: FragmentStartBinding?= null
private lateinit var weatherState: TextView
private lateinit var temperature: TextView
private lateinit var weatherTip: TextView
private lateinit var weatherIcon: ImageView
private lateinit var mLocationManager: LocationManager
private lateinit var mLocationListener: LocationListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val fragmentBinding = FragmentStartBinding.inflate(inflater, container, false)
binding = fragmentBinding
return fragmentBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.startFragment = this
binding?.apply {
temperature = temperatureTv
weatherState = weatherTv
weatherTip = weatherTipTv
weatherIcon = weatherIc
}
}
override fun onResume() {
super.onResume()
getWeatherInCurrentLocation()
}
private fun getWeatherInCurrentLocation(){
mLocationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
mLocationListener = LocationListener { p0 ->
val params: RequestParams = RequestParams()
params.put("lat", p0.latitude)
params.put("lon", p0.longitude)
params.put("appid", Companion.API_KEY)
doNetworking(params)
}
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(requireActivity(), arrayOf<String>(Manifest.permission.ACCESS_FINE_LOCATION), WEATHER_REQUEST)
return
}
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, MIN_TIME, MIN_DISTANCE, mLocationListener)
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME, MIN_DISTANCE, mLocationListener)
}
private fun doNetworking(params: RequestParams) {
var client = AsyncHttpClient()
client.get(WEATHER_URL, params, object: JsonHttpResponseHandler(){
override fun onSuccess(
statusCode: Int,
headers: Array<out Header>?,
response: JSONObject?
) {
val weatherData = WeatherData().fromJson(response)
if (weatherData != null) {
updateWeather(weatherData)
}
}
})
}
private fun updateWeather(weather: WeatherData) {
temperature.setText(weather.tempString+" ℃")
weatherState.setText(weather.weatherType)
val resourceID = resources.getIdentifier(weather.icon, "drawable", activity?.packageName)
weatherIcon.setImageResource(resourceID)
}
override fun onPause() {
super.onPause()
if(mLocationManager!=null){
mLocationManager.removeUpdates(mLocationListener)
}
}
}
API_KEY
에는 회원가입 후 발급 된 개인 API Key를 입력한다.
날씨 불러오기
getWeatherInCurrentLocation()
바인딩등 날씨 불러오기에 중요하지 않은 부분은 넘어가고 onResume()
에서 호출되는 getWeatherInCurrentLocation()
부터 보자.
private fun getWeatherInCurrentLocation(){
mLocationManager = requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
mLocationListener = LocationListener { p0 ->
val params: RequestParams = RequestParams()
params.put("lat", p0.latitude)
params.put("lon", p0.longitude)
params.put("appid", API_KEY)
doNetworking(params)
}
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(requireActivity(), arrayOf<String>(Manifest.permission.ACCESS_FINE_LOCATION), WEATHER_REQUEST)
return
}
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, MIN_TIME, MIN_DISTANCE, mLocationListener)
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME, MIN_DISTANCE, mLocationListener)
}
params
에 정보 요청에 필요한 파라미터를 담아doNetworking()
을 호출한다.- 권한이 허용되지 않았다면
ActivityCompat.requestPermissions
이 호출되면서 위치 액세스 허용을 묻는 창이 뜬다.
- LocationManager는
MIN_TIME
이나MIN_DISTANCE
가 지나면 정보 업데이트를 요청한다.
doNetworking()
private fun doNetworking(params: RequestParams) {
var client = AsyncHttpClient()
client.get(WEATHER_URL, params, object: JsonHttpResponseHandler(){
override fun onSuccess(
statusCode: Int,
headers: Array<out Header>?,
response: JSONObject?
) {
val weatherData = WeatherData().fromJson(response)
if (weatherData != null) {
updateWeather(weatherData)
}
}
})
}
RequestParams
에 담긴 파라미터를 이용해JSONObject
의response
를 받아온다. 그 다음WeatherData
에 전달해 이곳에 담긴 정보로 UI를 업데이트 하게된다.
updateWeather()
WeatherData 클래스를 보기에 앞서 WeatherData 클래스를 거쳐서 날씨 정보가 weatherData
에 담기는데, updateWeather()
는 이것을 전달받아 뷰를 업데이트 한다.
private fun updateWeather(weather: WeatherData) {
temperature.setText(weather.tempString+" ℃")
weatherState.setText(weather.weatherType)
val resourceID = resources.getIdentifier(weather.icon, "drawable", activity?.packageName)
weatherIcon.setImageResource(resourceID)
}
- drawable에는 날씨 분류에 해당하는 아이콘 이미지가 있다.
WeatherData
이곳에서 response
에 담긴 정보에서 필요한 정보를 가져오고, 그 정보를 바탕으로 weather condition에 해당하는 아이콘을 선택한다.
API response가 어떤 형태로 되어있는지는 여기(Weather fields in API response)에서 확인할 수 있다.
사이트의 예시를 보면 날씨와 기온 외에도 최저기온, 최고기온, 습도 등 다양한 정보를 가져올 수 있다는 것을 알 수 있다.
Weather condition code는 여기에서 확인할 수 있다.
class WeatherData{
lateinit var tempString: String
lateinit var icon: String
lateinit var weatherType: String
private var weatherId: Int = 0
private var tempInt: Int =0
fun fromJson(jsonObject: JSONObject?): WeatherData? {
try{
var weatherData = WeatherData()
weatherData.weatherId = jsonObject?.getJSONArray("weather")?.getJSONObject(0)?.getInt("id")!!
weatherData.weatherType = jsonObject.getJSONArray("weather").getJSONObject(0).getString("main")
weatherData.icon = updateWeatherIcon(weatherData.weatherId)
val roundedTemp: Int = (jsonObject.getJSONObject("main").getDouble("temp")-273.15).toInt()
weatherData.tempString = roundedTemp.toString()
weatherData.tempInt = roundedTemp
return weatherData
}catch (e: JSONException){
e.printStackTrace()
return null
}
}
private fun updateWeatherIcon(condition: Int): String {
if (condition in 200..299) {
return "thunderstorm"
} else if (condition in 300..499) {
return "lightrain"
} else if (condition in 500..599) {
return "rain"
} else if (condition in 600..700) {
return "snow"
} else if (condition in 701..771) {
return "fog"
} else if (condition in 772..799) {
return "overcast"
} else if (condition == 800) {
return "clear"
} else if (condition in 801..804) {
return "cloudy"
} else if (condition in 900..902) {
return "thunderstorm"
}
if (condition == 903) {
return "snow"
}
if (condition == 904) {
return "clear"
}
return if (condition in 905..1000) {
"thunderstorm"
} else "dunno"
}
}
weatherId
에는 정수 형태의 weather condition code가 담기는데 여기에서 각 코드의 의미를 알 수 있다. 예를 들어500
은 Rain 카테고리에 해당하기 때문에updateWeatherIcon()
이"rain"
을 반환한다.
반환된 문자열은 대응하는 아이콘을 찾는데 사용된다.- 기온은 double 타입이기 때문에 사용자가 보기 좋게 정수 타입으로 바꿔준 후 변수에 담는다.
Author And Source
이 문제에 관하여(날씨 정보 불러오기 (OpenWeathermap API)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@emily2307/날씨-정보-불러오기-OpenWeathermap-API저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)