개발 일지/C#

MVVM 패턴을 활용한 AccuWeather API 날씨 검색 앱 정리

DDD Developer 2024. 12. 2. 20:47
728x90
반응형

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가 동기화.

 

728x90
반응형