seong

Weather앱 만들기 - 날씨 API연동 (GetX,MVVM) 본문

Flutter/Flutter

Weather앱 만들기 - 날씨 API연동 (GetX,MVVM)

hyeonseong 2023. 10. 13. 13:45

1. 아래 링크 회원 가입 및 APIKey발급

https://openweathermap.org/

2. API와 통신을 위해 http_connect 생성

- 싱글톤 패턴을 사용해 중복된 인스턴스를 사용하지 못하도록 방지

- API 요청할 주소 : "http://api.openweathermap.org/data/2.5/weather?lat=$lat&lon=$lon&appid=$_apiKey&units=metric"

- path를 uri 타입에 맞게 파싱하여 get요청

- 응답받은 Json String -> Dart에서 다룰 수 있게 Json자료형(Map<String,dynamic>)으로 변환 해서 넘겨준다.

class HttpConnector {
// 싱글톤 패턴 사용
  static final HttpConnector _instance = HttpConnector._internal();
  factory HttpConnector() => _instance;
  HttpConnector._internal();

  final String _apiKey = "발급 받은 APIkey";

  Future<dynamic> getWeatherData({required String lat, required String lon}) async {
    final String path = "http://api.openweathermap.org/data/2.5/weather?lat=$lat&lon=$lon&appid=$_apiKey&units=metric";
    Uri uri = Uri.parse(path);
    Response response = await get(uri);

    if (response.statusCode == 200) {
      Logger().d("response.body : ${response.body}");
      return jsonDecode(response.body);
    } else {
      Logger().d("Error Status | ${response.statusCode}");
      Logger().d("Error response body: ${response.body}");
    }
  }
}

3. weather_Service

- http_connect를 호출 및 요청

- fromJson : 함수를 통해 내가 사용할 수 있도록 Map<String,dynamic>타입을 내가 만든 객체로 변환 시켜준다.

class WeatherService {
  static final WeatherService _instance = WeatherService._internal();
  factory WeatherService() => _instance;
  WeatherService._internal();

  HttpConnector httpConnector = HttpConnector();

  Future<WeatherModel> fetchWeather(String lat, String lon) async {
    final weatherData = await httpConnector.getWeatherData(lat: lat, lon: lon);
    return WeatherModel.fromJson(weatherData);
  }
}

4. WeatherModel를 만들어주기 (MVVM의 Model)

- connect에서 통신해 받은 response.body를 확인해 보면 아래 처럼 쭉 나온다.

이 값들을 복사해서 https://app.quicktype.io/ 여기에 그냥 넣어주기만 하면 알아서 만들어준다.(fromJson, toJson해주기가 너무 편해진다..)

아래 Json을 보면 위도와 경도가 이상하게 나오는데 이유는 IOS 시뮬레이터는 현재 위치를 자동으로 잡아주지 않고, 직접 수정 해주어야한다고 한다. 실기기로 확인 했을땐 위도 경도 현재 위치가 제대로 나오는걸 확인 했다!

만들어진 WeatherModel, 굳이 모든 데이터를 파싱 하지 않고 필요한 부분만 골라서 사용해도 되지만 우선 어떤 데이터가 있는지와 모든 데이터가 있으면 혹시 나중에 필요해질 수 있어서 다 넣었다

class WeatherModel {
  Coord? coord;
  List<Weather>? weather;
  String? base;
  Main? main;
  int? visibility;
  Wind? wind;
  Clouds? clouds;
  int? dt;
  Sys? sys;
  int? timezone;
  int? id;
  String? name;
  int? cod;

  WeatherModel({
    this.coord,
    this.weather,
    this.base,
    this.main,
    this.visibility,
    this.wind,
    this.clouds,
    this.dt,
    this.sys,
    this.timezone,
    this.id,
    this.name,
    this.cod,
  });

  factory WeatherModel.fromJson(Map<String, dynamic> json) => WeatherModel(
        coord: Coord.fromJson(json["coord"]),
        weather: List<Weather>.from(json["weather"].map((x) => Weather.fromJson(x))),
        base: json["base"],
        main: Main.fromJson(json["main"]),
        visibility: json["visibility"],
        wind: Wind.fromJson(json["wind"]),
        clouds: Clouds.fromJson(json["clouds"]),
        dt: json["dt"],
        sys: Sys.fromMap(json["sys"]),
        timezone: json["timezone"],
        id: json["id"],
        name: json["name"],
        cod: json["cod"],
      );

  Map<String, dynamic> toJson() => {
        "coord": coord!.toJson(),
        "weather": List<dynamic>.from(weather!.map((x) => x.toJson())),
        "base": base,
        "main": main!.toJson(),
        "visibility": visibility,
        "wind": wind!.toJson(),
        "clouds": clouds!.toJson(),
        "dt": dt,
        "sys": sys!.toMap(),
        "timezone": timezone,
        "id": id,
        "name": name,
        "cod": cod,
      };
}

5. ViewModel 생성 ( MVVM의 ViewModel )

주로 RiverPod를 사용하지만, GetX도 한번 사용해보고 싶어서 이번엔 GetX를 사용해 주었다. 

- 상태관리를 위한 Rx타입으로 선언 하고 .obs를 붙여준다.(Rx는 observable 변수, 즉 상태를 관찰한다.)

- .value를 붙혀 값에 접근 할 수있도록 해주었다.

- 현재 위치를 구하기 위해 Gelocator 패키지를 사용 

- isLocationServiceEnabled : 위치 서비스가 활성화 되어 있는지 확인

- checkPermission : 위치 서비스 권한 확인

- requestPermission : 권한을 요청 

만약 이때 수락 하지 않았다면, 위치 서비스가 실행 되지 않고 앱 재실행 시 다시 허가 해주어야한다.

- getCurrentPosition : 현재 위치를 가져와준다. high는 높은 정확도 이지만 전력 사용율이 높다고 한다.

high외에 low, medium등을 사용할 수 있다.

결과 값으로 위도와 경도를 받아서 api주소에 요청 시 현재 위치를 기입 해준다. 

class WeatherViewModel extends GetxController {
  WeatherService weatherService = WeatherService();

  //Model
  final Rx<WeatherModel> _weatherModel = WeatherModel().obs;
  WeatherModel get weatherModel => _weatherModel.value;

  @override
  void onInit() {
    super.onInit();
    featchWeather();
  }

Future<void> featchWeather() async {
    // 위치 서비스가 활성화되어 있는지 확인
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      Logger().d("위치 서비스가 활성화 되어 있지않습니다.");
      return;
    }
    // 위치 서비스 권한 확인
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      // 권한 요청
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        Logger().d("위치 권한을 허가해주세요.");
        return;
      }
    }
    // 현재 위치 가져오기
    var currentPosition = await Geolocator.getCurrentPosition(
      desiredAccuracy: LocationAccuracy.high,
      timeLimit: const Duration(
        seconds: 3,
      ),
    );
    // 위도와 경도
    String latitude = currentPosition.latitude.toString();
    String longitude = currentPosition.longitude.toString();
    final weatherData = await weatherService.fetchWeather(latitude, longitude);

    _weatherModel.value = weatherData;
  }
}

6. UI ( MVVM의 View) 

- Get.put(WeatherViewModel()) : 상태관리 Controller를 등록

최초에 인스턴스를 등록후 다른 위젯에서는 Get.find만 해주어도 된다.

class _HomeScreenState extends State<HomeScreen> {
  bool isSwitchOn = false;
  final WeatherViewModel model = Get.put(WeatherViewModel());
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: isSwitchOn ? Colors.black : Colors.white,
      appBar: const CustomAppBar(),
      body: Column(
        children: [
          Container(
            height: 450,
            width: 393,
            decoration: const BoxDecoration(
              gradient: LinearGradient(
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                colors: [
                  Colors.white,
                  Color(0xFFffeceb),
                ],
              ),
            ),
            child: GetX<WeatherViewModel>(
              builder: (_) => WeatherCard(
                day: model.weatherModel.timezone.toString(),
                //day: "10월 4일 수요일",
                //temperature: "25도",
                temperature: model.weatherModel.main!.humidity.toString(),
                // weather: "흐림",
                weather: model.weatherModel.id.toString(),
              ),
            ),
          ),
          TextButton(onPressed: () {}, child: const Text("날씨 확인")),
        ],
      ),
    );
  }
}

 

'Flutter > Flutter' 카테고리의 다른 글

Flutter - Weather앱 TabBar 만들기  (0) 2023.10.18
Flutter - Google font 적용  (1) 2023.10.16
Flutter - Pro Animate 3D 입체감 살리기  (0) 2023.09.14
Flutter - SideMenu  (0) 2023.09.12
Flutter -RiveAnimation Icon (2)  (0) 2023.09.12