MVVM 패턴을 활용한 AccuWeather API 날씨 검색 앱 정리
1. MVVM 패턴의 구성 요소
Model (모델)
데이터 구조를 정의하고 데이터 저장소와 연결되는 부분.
데이터베이스 접근, API 호출, 데이터 연산을 처리.
예제: City, Weather 클래스는 API로부터 가져온 데이터를 정의.
View (뷰)
사용자와 상호작용하는 UI를 정의.
XAML에서 데이터와 UI 컨트롤을 바인딩.
예제: Weather.xaml은 사용자가 날씨를 검색하고 결과를 표시.
ViewModel (뷰모델)
View와 Model 사이에서 데이터를 가공하여 View에 전달.
View와 바인딩되어 사용자 입력을 처리하고 데이터를 업데이트.
예제: WeatherVM은 날씨 데이터를 받아 View에 전달하고, 사용자 입력을 API 호출로 처리.
2. 프로젝트 구성
Model: 데이터를 표현하는 구조체
City.cs: 도시 데이터를 정의.
Weather.cs: 날씨 데이터를 정의.
ViewModel: 데이터와 로직을 연결
AccuWeatherHelper.cs: API 호출 로직.
WeatherVM.cs: View와 데이터를 연결, API 호출 결과를 처리.
View: 사용자와 상호작용하는 UI
Weather.xaml: UI 레이아웃과 데이터 바인딩 정의.
3. 코드 정리
3.1 Model 정의 API에서 가져오는 JSON 데이터를 클래스로 변환하여 활용.
City.cs
namespace WeatherApp.Model
{
// 국가나 지역 정보를 담는 클래스
public class Area
{
public string ID { get; set; } // 고유 ID
public string LocalizedName { get; set; } // 지역 이름
}
// 도시 정보를 담는 클래스
public class City
{
public int Version { get; set; } // 데이터 버전
public string Key { get; set; } // 도시를 식별하는 고유 Key
public string Type { get; set; } // 도시 타입 (예: Urban)
public int Rank { get; set; } // 도시 순위 (검색 결과 우선순위)
public string LocalizedName { get; set; } // 도시 이름
public Area Country { get; set; } // 국가 정보
public Area AdministrativeArea { get; set; } // 행정 구역 정보
}
}
Weather.cs
namespace WeatherApp.Model
{
// 온도 정보를 담는 클래스
public class Units
{
public int Value { get; set; } // 온도 값
public string Unit { get; set; } // 온도의 단위 (예: °C)
public int UnitType { get; set; } // 단위 유형 (예: 17 = 섭씨)
}
// 온도 데이터 클래스
public class Temperature
{
public Units Metric { get; set; } // 섭씨 온도
public Units Imperial { get; set; } // 화씨 온도
}
// 현재 날씨 상태를 담는 클래스
public class CurrentConditions
{
public DateTime LocalObservationDateTime { get; set; } // 관측 시간
public string WeatherText { get; set; } // 날씨 설명 (예: 맑음)
public Temperature Temperature { get; set; } // 온도 데이터
}
}
3.2 ViewModel 정의
AccuWeatherHelper.cs API 호출 로직과 데이터를 JSON에서 모델로 변환.
using Newtonsoft.Json; // JSON 데이터를 쉽게 파싱하기 위한 라이브러리
using System.Net.Http; // API 요청을 위해 사용
using WeatherApp.Model; // Model 클래스 포함
namespace WeatherApp.ViewModel.Helpers
{
public class AccuWeatherHelper
{
// API 키 (AccuWeather에서 발급)
public const string API_KEY = "Your_API_Key";
public const string BASE_URL = "http://dataservice.accuweather.com/";
public const string LANGUAGE = "ko-kr"; // 한국어
// 도시 검색 API Endpoint
public const string AUTOCOMPLETE_ENDPOINT =
"locations/v1/cities/autocomplete?apikey={0}&q={1}&language={2}";
// 현재 날씨 API Endpoint
public const string CURRENT_CONDITIONS_ENDPOINT =
"currentconditions/v1/{0}?apikey={1}&language={2}";
// 도시 데이터를 API에서 가져오는 메서드
public static async Task<List<City>> GetCities(string query)
{
// API URL 구성
string url = BASE_URL + string.Format(AUTOCOMPLETE_ENDPOINT, API_KEY, query, LANGUAGE);
using (HttpClient clnt = new HttpClient())
{
var res = await clnt.GetAsync(url); // API 요청
string json = await res.Content.ReadAsStringAsync(); // JSON 결과를 문자열로 변환
return JsonConvert.DeserializeObject<List<City>>(json); // JSON을 City 리스트로 변환
}
}
// 현재 날씨 데이터를 API에서 가져오는 메서드
public static async Task<CurrentConditions> GetCurrentConditions(string cityKey)
{
// API URL 구성
string url = BASE_URL + string.Format(CURRENT_CONDITIONS_ENDPOINT, cityKey, API_KEY, LANGUAGE);
using (HttpClient clnt = new HttpClient())
{
var res = await clnt.GetAsync(url); // API 요청
string json = await res.Content.ReadAsStringAsync(); // JSON 결과를 문자열로 변환
// JSON을 CurrentConditions 리스트로 변환 후 첫 번째 항목 반환
return JsonConvert.DeserializeObject<List<CurrentConditions>>(json).FirstOrDefault();
}
}
}
}
WeatherVM.cs ViewModel에서 View와 데이터를 바인딩.
using System.Collections.ObjectModel; // ObservableCollection을 사용
using System.ComponentModel; // INotifyPropertyChanged 인터페이스
using System.Runtime.CompilerServices; // CallerMemberName 속성
using WeatherApp.Model; // Model 클래스
using WeatherApp.ViewModel.Helpers; // Helper 클래스
namespace WeatherApp.ViewModel
{
public class WeatherVM : INotifyPropertyChanged
{
// 사용자 입력 (도시 검색어)
private string query;
public string Query
{
get => query;
set
{
query = value;
OnPropertyChanged(); // 속성 변경 알림
}
}
// 선택된 도시
private City selectedCity;
public City SelectedCity
{
get => selectedCity;
set
{
selectedCity = value;
OnPropertyChanged();
GetCurrentConditions(); // 도시 선택 시 날씨 정보 가져오기
}
}
// 현재 날씨 정보
private CurrentConditions currentConditions;
public CurrentConditions CurrentConditions
{
get => currentConditions;
set
{
currentConditions = value;
OnPropertyChanged(); // 속성 변경 알림
}
}
// 도시 목록 (검색 결과)
public ObservableCollection<City> Cities { get; set; }
public WeatherVM()
{
Cities = new ObservableCollection<City>(); // 초기화
}
// 도시 검색 메서드
public async void SearchCities()
{
var cities = await AccuWeatherHelper.GetCities(Query); // API 호출
Cities.Clear(); // 기존 데이터 초기화
foreach (var city in cities)
Cities.Add(city); // 검색 결과 추가
}
// 현재 날씨 정보 가져오기
public async void GetCurrentConditions()
{
if (SelectedCity != null)
CurrentConditions = await AccuWeatherHelper.GetCurrentConditions(SelectedCity.Key);
}
// 속성 변경 알림 이벤트
public event PropertyChangedEventHandler PropertyChanged;
// 속성 변경 시 호출
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
3.3 View 정의 (Weather.xaml)
<Window x:Class="WeatherApp.View.Weather"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WeatherApp.ViewModel"
Title="Weather" Height="600" Width="400">
<!-- ViewModel 리소스 등록 -->
<Window.Resources>
<vm:WeatherVM x:Key="vm" />
</Window.Resources>
<!-- ViewModel과 연결 -->
<Grid DataContext="{StaticResource vm}">
<!-- 도시 검색어 입력 -->
<TextBox Text="{Binding Query, Mode=TwoWay}" Width="200" Margin="20" />
<!-- 검색 버튼 -->
<Button Content="Search" Command="{Binding SearchCommand}" Width="100" Margin="20" />
<!-- 도시 목록 표시 -->
<ListBox ItemsSource="{Binding Cities}"
SelectedItem="{Binding SelectedCity}" DisplayMemberPath="LocalizedName" />
<!-- 현재 날씨 정보 -->
<StackPanel DataContext="{Binding CurrentConditions}">
<!-- 날씨 설명 -->
<TextBlock Text="{Binding WeatherText}" FontSize="20" />
<!-- 온도 -->
<TextBlock Text="{Binding Temperature.Metric.Value, StringFormat={}{0}°C}" FontSize="20" />
</StackPanel>
</Grid>
</Window>
동작 흐름
사용자가 도시명을 입력 → Query 속성에 값이 업데이트.
SearchCities() 호출 → API에서 도시 목록 조회 후 Cities 업데이트.
도시 선택 → SelectedCity 속성이 업데이트되며 날씨 조회 실행.
날씨 데이터 업데이트 → CurrentConditions와 UI가 동기화.