728x90
반응형
카카오맵의 라벨 기능을 이용해 맵 위에 현재 날씨 아이콘을 띄우도록 하려고 한다
먼저 Map 매니저 클래스에서 UI 담당 클래스를 분리작업을 했다
MapViewMager > MapController 변경
Map의 초기화 및 라이프사이클 관리, 카메라 이동 기능만을 담당하도록 코드를 수정하였다
package com.example.weatherapp.map
import ...
/***
* 맵의 초기화, 라이프사이클 관리, 카메라 이동
*/
class MapController(
private val activity: AppCompatActivity,
private val mapView: MapView,
) {
private var kakaoMap: KakaoMap? = null
fun init(onMapReady: (KakaoMap) -> Unit)
{
KakaoMapSdk.init(activity, "47267159ba04526c395cbc28462c33ea")
Log.d("MapManager", KakaoMapSdk.INSTANCE.toString() )
mapView.start(object : MapLifeCycleCallback() {
override fun onMapDestroy() {
// 지도 API 가 정상적으로 종료될 때 호출됨
}
override fun onMapError(error: Exception) {
// 인증 실패 및 지도 사용 중 에러가 발생할 때 호출됨
Log.e("MapManager", "Map error: ${error.localizedMessage}")
}
}, object : KakaoMapReadyCallback() {
override fun onMapReady(map: KakaoMap) {
// 인증 후 API 가 정상적으로 실행될 때 호출됨
kakaoMap = map
onMapReady(map)
}
})
}
fun onResume()
{
mapView.resume()
}
fun onPause()
{
mapView.pause()
}
//카메라 이동
fun moveCamera(position: LatLng, zoomLevel: Int = 10) {
kakaoMap?.moveCamera(CameraUpdateFactory.newCenterPosition(position, zoomLevel))
}
fun getKakaoMap(): KakaoMap? {
return kakaoMap
}
}
MapUIManager 추가
import ...
/***
* 맵의 UI 요소를 지도에 추가/갱신
*/
class MapUIManager(
private val activity: AppCompatActivity,
private val kakaoMap: KakaoMap,
private val mapController: MapController,
private val locationDataStore: LocationDataStore,
private val weatherUIMapper: WeatherUIMapper
) {
private var currentLatLng: LatLng? = null // 현 위치 캐싱
private var currentLabel: Label? = null // 라벨 값 캐싱
init {
//현 위치와 날씨 아이콘 초기화
updateCurPosition()
observeWeatherIconChanges()
}
private fun observeWeatherIconChanges() {
// weatherIcon 관찰 및 업데이트
weatherUIMapper.weatherIcon.observe(activity) { newIconRes ->
updateWeatherLabel(newIconRes)
}
}
private fun updateCurPosition() {
activity.lifecycleScope.launch {
val curLatitude = locationDataStore.cur_latitude.first()
val curLongitude = locationDataStore.cur_longitude.first()
if (curLatitude != null && curLongitude != null) {
currentLatLng = LatLng.from(curLatitude, curLongitude)
// 맵의 카메라를 현재 위치로 이동
mapController.moveCamera(currentLatLng!!, 13)
}
}
}
private fun updateWeatherLabel(iconResId: Int? = null) {
currentLatLng?.let {
// 새로운 라벨 스타일 설정 (맵퍼에서 가져온 아이콘 적용)
val iconRes = iconResId ?: weatherUIMapper.weatherIcon.value ?: R.drawable.sunny_128
val styles = kakaoMap.labelManager?.addLabelStyles(
LabelStyles.from(LabelStyle.from(iconRes).setAnchorPoint(0.5f, 1.0f))
)
// 새로운 라벨 추가
val options = LabelOptions.from(it).setStyles(styles)
currentLabel = kakaoMap.labelManager?.layer?.addLabel(options)
}
}
}
- 추가된 updateWeatherLabel()을 통해 날씨 아이콘 업데이트
그 외에 변경되거나 추가된 클래스
- WeatherViewModel
- 날씨 API Response 부분 > WeatherRepository
- 날씨 아이콘 관찰 및 업데이트 > WeatherUIMapper
- 그 외의 기능 유지
- WeatherRepository 추가
- 날씨 API Response 담당
- WeatherUIManager 추가
- 날씨 API 데이터 관찰을 통한 날씨 관련 UI 업데이트
- WeatherViewModelFactory 추가
- WeatherViewModel이 WeatherRepository를 매개변수로 사용하기 위해 추가
WeatherRepository 추가
package com.example.weatherapp.weather
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
/***
* Weather API response 관리
*/
class WeatherRepository {
private val _nowcastData = MutableLiveData<NcstWeatherResponse>() // 실황 데이터 캐싱
val nowcastData: LiveData<NcstWeatherResponse> get() = _nowcastData
private val _forecastData = MutableLiveData<FcstWeatherResponse>() // 예보 데이터 캐싱
val forecastData: LiveData<FcstWeatherResponse> get() = _forecastData
suspend fun fetchNowcastData(serviceKey: String, nx: Int, ny: Int) {
val (baseDate, baseTime) = WeatherUtils.getCurrentNcstBaseTime()
val response = RetrofitClient.instance.create(WeatherApi::class.java)
.getWeather(serviceKey, baseDate = baseDate, baseTime = baseTime, nx = nx, ny = ny)
Log.d("WeatherRepository", "Nowcast API Response: $response")
_nowcastData.postValue(response) // LiveData 업데이트
}
suspend fun fetchForecastData(serviceKey: String, nx: Int, ny: Int) {
val (baseDate, baseTime) = WeatherUtils.getCurrentFcstBaseTime()
val response = RetrofitClient.instance.create(WeatherApi::class.java)
.getForecast(serviceKey, baseDate = baseDate, baseTime = baseTime, fcstDate = baseDate, fcstTime = baseTime, nx = nx, ny = ny)
Log.d("WeatherRepository", "Forecast API Response: $response")
_forecastData.postValue(response) // LiveData 업데이트
}
}
WeatherUIManager 추가
package com.example.weatherapp.weather
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
class WeatherUIManager(weatherViewModel: WeatherViewModel) {
private val _weatherIcon = MutableLiveData<Int>() // UI에서 사용할 날씨 아이콘
val weatherIcon: LiveData<Int> get() = _weatherIcon
init {
// 날씨 데이터가 변경될 때 아이콘 업데이트
val observer = Observer<Any> {
updateWeatherIcon(
weatherViewModel.skyCondition.value ?: 1,
weatherViewModel.precipitationType.value ?: 0
)
}
weatherViewModel.skyCondition.observeForever(observer)
weatherViewModel.precipitationType.observeForever(observer)
}
// 날씨 아이콘 업데이트
private fun updateWeatherIcon(skyCondition: Int, precipitationType: Int) {
_weatherIcon.value = WeatherUtils.getWeatherIcon128(skyCondition, precipitationType)
}
}
WeatherViewModelFactory 추가
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.weatherapp.weather.WeatherRepository
import com.example.weatherapp.weather.WeatherViewModel
class WeatherViewModelFactory(private val weatherRepository: WeatherRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(WeatherViewModel::class.java)) {
return WeatherViewModel(weatherRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
WeatherViewModel
package com.example.weatherapp.weather
import ...
class WeatherViewModel(private val weatherRepository: WeatherRepository) : ViewModel() {
private val _currentTemperature = MutableLiveData<String>() // 현재 온도 (T1H) - 초단기실황
val currentTemperature: LiveData<String> get() = _currentTemperature
private val _skyCondition = MutableLiveData<Int>() // 하늘 상태 (SKY) - 초단기예보
val skyCondition: LiveData<Int> get() = _skyCondition
private val _precipitationType = MutableLiveData<Int>() // 강수 형태 (PTY) - 초단기실황
val precipitationType: LiveData<Int> get() = _precipitationType
init {
// Repository의 데이터를 구독하여 ViewModel의 데이터 업데이트
weatherRepository.nowcastData.observeForever { updateNowcastData(it) }
weatherRepository.forecastData.observeForever { updateForecastData(it) }
}
// 날씨 데이터 가져오기
fun fetchWeatherData(serviceKey: String, nx: Int, ny: Int) {
Log.d("WeatherViewModel", "Fetching weather data for nx: $nx, ny: $ny")
viewModelScope.launch {
weatherRepository.fetchNowcastData(serviceKey, nx, ny)
weatherRepository.fetchForecastData(serviceKey, nx, ny)
}
}
// 초단기실황 데이터 업데이트
private fun updateNowcastData(response: NcstWeatherResponse) {
Log.d("WeatherViewModel", "ncst API Response: $response")
val body = response.response.body
if (body != null && body.items.item.isNotEmpty()) {
_precipitationType.value = body.items.item.find { it.category == "PTY" }?.obsrValue?.toIntOrNull() ?: 0
_currentTemperature.value = body.items.item.find { it.category == "T1H" }?.obsrValue ?: "N/A"
} else {
Log.e("WeatherViewModel", "Nowcast response body is empty")
}
}
// 초단기예보 데이터 업데이트
private fun updateForecastData(response: FcstWeatherResponse) {
Log.d("WeatherViewModel", "fcst API Response: $response")
val body = response.response.body
if (body != null && body.items.item.isNotEmpty()) {
_skyCondition.value = body.items.item.find { it.category == "SKY" }?.fcstValue?.toIntOrNull() ?: 1
} else {
Log.e("WeatherViewModel", "Forecast response body is empty")
}
}
}
맵 내의 날씨 라벨이 추가된 모습
728x90
반응형
'간단한 안드로이드 앱 만들기 (날씨 앱)' 카테고리의 다른 글
12. 검색 액티비티에 카드 레이아웃 추가 (0) | 2025.02.24 |
---|---|
11. 도시 검색 버튼 추가 (1) | 2025.02.21 |
9. 지도 나타내기 (카카오맵 API) (0) | 2025.02.20 |
8. 날씨 아이콘 바꾸기 (1) | 2025.02.13 |
7. UI 업데이트 기능 분리 및 날씨 계산 유틸 분리 (중간정리) (0) | 2025.02.12 |