날씨 알려주는 앱

장윤찬·2021년 12월 13일
0
  • 제공할 서비스
    현재 디바이스 위치의 날씨정보 제공
    다른 도시검색 및 검색한 도시의 날씨정보 제공
    현재위치 초기화
  • 해야할것
    디바이스의 위치정보 가져오기
    날씨데이터 가져오기(open weathermap사이트 API이용)
    화면 및 기능 구현 & 페이지간 데이터 전달

위치정보 가져오기

geolcator 패키지 불러오기

geolcator 패키지를 통해 디바이스의 위치좌표를 구해야한다.
https://pub.dev/packages/geolocator

  • pubspec.yami파일
dependencies:
  geolocator: ^8.0.0
  • geolocator 패키지 import
import 'package:geolocator/geolocator.dart';
  • AndroidManifest.xml파일 (permission 설정)

현재 앱이 디바이스의 위치를 탐색하도록 허락해야한다.

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Location 클래스 내에 위치좌표 구하는 getCurrentLocation 메소드 작성 (location.dart 파일)

import 'package:geolocator/geolocator.dart';

class Location {
  double latitude;
  double longitude;
  Future<void> getCurrentLocation() async {
    try {
      LocationPermission permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied) {
        permission = await Geolocator.requestPermission();
      }
      Position position = await Geolocator.getCurrentPosition(
          desiredAccuracy: LocationAccuracy.low);
      latitude = position.latitude;
      longitude = position.longitude;
    } catch (e) {
      print(e);
    }
  }
}

날씨데이터 가져오기

  • Fetch data from the internet

플러터 문서에 외부 데이터를 가져오는 방법이 나와있다.
https://docs.flutter.dev/cookbook/networking/fetch-data

Add the http package

http 패키지를 불러옴으로써 외부 사이트의 API를 통해 데이터를 가져올 수 있다.
https://pub.dev/packages/http

  • pubspec.yami파일
dependencies:
  http: <latest_version>
  • Import the http package.
import 'package:http/http.dart' as http;
  • AndroidManifest.xml파일
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />

import 'dart:convert'

  • dart:convert import
import 'dart:convert';

'dart:convert'를 통해 가져온 JSON데이터의 parsing이 가능하다.

openweathermap 사이트 API

openweathermap 사이트의 날씨데이터에 접근하는 API
https://openweathermap.org/current#one

  • 특정 좌표값의 날씨데이터 제공

  • 특정 도시명의 좌표값 제공

API key 발급

API를 이용하기 위해 API key가 필요하다.
가입후 API key를 발급받자.
https://home.openweathermap.org/api_keys

NetworkHelper 클래스내에 날씨데이터를 구하는 getData 메소드 작성 (networking.dart 파일)

  • url 속성에
    API를 대입할 것이다.
  • http.get
    데이터를 가져오는 메소드
  • response.body
    날씨데이터가 저장된 부분(JSON형식)
  • jsonDecode
    특정 날씨정보만 사용할 수 있도록 parsing이 가능하게 해준다.
import 'package:http/http.dart' as http;
import 'dart:convert';

class NetworkHelper {
  NetworkHelper(this.url);
  final String url;
  Future getData() async {
    http.Response response = await http.get(
      Uri.parse(url),
    );
    if (response.statusCode == 200) {
      String data = response.body;
      return jsonDecode(data);
    } else {
      print(response.statusCode);
    }
  }
}

WeatherModel 클래스내에 날씨데이터를 반환하는 메소드작성, 날씨컨디션 값과 온도값에 따라 다른 텍스트를 반환하는 메소드 작성 (weather.dart 파일)

getCityWeather : 도시명에 따라 날씨데이터를 반환하는 메소드
getLocationWeather : 특정 좌표에 따라 날씨데이터를 반환하는 메소드

import '../services/location.dart';
import 'package:clima/services/networking.dart';

const apiKey = '30882e584a2b5be7392dfd33774bf53b';
const openWeatherMapURL = 'https://api.openweathermap.org/data/2.5/weather';

class WeatherModel {
  Future<dynamic> getCityWeather(String cityName) async {
    var url = '$openWeatherMapURL?q=$cityName&appid=$apiKey&units=metric';
    NetworkHelper networkHelper = NetworkHelper(url);
    var weatherData = await networkHelper.getData();
    return weatherData;
  }

  Future<dynamic> getLocationWeather() async {
    Location location = Location();
    await location.getCurrentLocation();
    NetworkHelper networkHelper = NetworkHelper(
        '$openWeatherMapURL?lat=${location.latitude}&lon=${location.longitude}&appid=$apiKey&units=metric');
    var weatherData = await networkHelper.getData();
    return weatherData;
  }

  String getWeatherIcon(int condition) {
    if (condition < 300) {
      return '🌩';
    } else if (condition < 400) {
      return '🌧';
    } else if (condition < 600) {
      return '☔️';
    } else if (condition < 700) {
      return '☃️';
    } else if (condition < 800) {
      return '🌫';
    } else if (condition == 800) {
      return '☀️';
    } else if (condition <= 804) {
      return '☁️';
    } else {
      return '🤷‍';
    }
  }

  String getMessage(int temp) {
    if (temp > 25) {
      return 'It\'s 🍦 time';
    } else if (temp > 20) {
      return 'Time for shorts and 👕';
    } else if (temp < 10) {
      return 'You\'ll need 🧣 and 🧤';
    } else {
      return 'Bring a 🧥 just in case';
    }
  }
}

화면 및 기능 구현

main.dart 파일

테마지정 및 LoadingScreen 페이지 시작

import 'package:flutter/material.dart';
import 'package:clima/screens/loading_screen.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: LoadingScreen(),
    );
  }
}

로딩화면(loading_screen.dart)

flutter_spinkit 패키지 불러오기

https://pub.dev/packages/flutter_spinkit

flutter_spinkit 패키지는 여러 로딩 애니메이션 이미지를 제공한다.

  • initState()메소드안의 getLocationData() 메소드
    위치좌표에 따른 날씨데이터를 반환하고 LocationScreen()페이지로 이동한다. 또한, LocationScreen의 locationWearher속성에 날씨데이터를 전달한다.
import 'package:flutter/material.dart';
import 'location_screen.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:clima/services/weather.dart';

class LoadingScreen extends StatefulWidget {
  
  _LoadingScreenState createState() => _LoadingScreenState();
}

class _LoadingScreenState extends State<LoadingScreen> {
  
  void initState() {
    super.initState();
    getLocationData();
  }

  void getLocationData() async {
    var weatherData = await WeatherModel().getLocationWeather();

    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) {
        return LocationScreen(locationWeather: weatherData);
      }),  // MaterialPageRoute
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SpinKitDoubleBounce(
          color: Colors.white,
          size: 100,
        ),  // SpinKitDoubleBounce
      ),  Center
    );  // Scaffold
  }
}

날씨제공화면 (location_screen.dart 파일)

  • widget.locationWeather
    StatefulWidget이 전달받은 locationWeather 속성을 State위젯이 사용할 수 있다.
import 'package:flutter/material.dart';
import 'package:clima/utilities/constants.dart';
import 'package:clima/services/weather.dart';
import 'city_screen.dart';

class LocationScreen extends StatefulWidget {
  final locationWeather;
  LocationScreen({this.locationWeather});
  
  _LocationScreenState createState() => _LocationScreenState();
}

class _LocationScreenState extends State<LocationScreen> {
  WeatherModel weather = WeatherModel();

  int temperature;
  String cityName;
  String weatherIcon;
  String weatherMessage;

  
  void initState() {
    super.initState();
    updateUI(widget.locationWeather);
  }

  void updateUI(var weatherData) {
    setState(() {
      if (weatherData == null) {
        temperature = 0;
        weatherIcon = 'Error';
        weatherMessage = 'Unable to get weather data';
        cityName = '';
        return;
      }
      double temp = weatherData['main']['temp'];
      temperature = temp.toInt();
      int condition = weatherData['weather'][0]['id'];
      weatherMessage = weather.getMessage(temperature);
      weatherIcon = weather.getWeatherIcon(condition);
      cityName = weatherData['name'];
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: AssetImage('images/location_background.jpg'),
            fit: BoxFit.cover,
            colorFilter: ColorFilter.mode(
                Colors.white.withOpacity(0.8), BlendMode.dstATop),
          ),
        ),
        constraints: BoxConstraints.expand(),
        child: SafeArea(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  FlatButton(
                    onPressed: () async {
                      var weatherData = await weather.getLocationWeather();
                      updateUI(weatherData);
                    },
                    child: Icon(
                      Icons.near_me,
                      size: 50.0,
                    ),
                  ),
                  FlatButton(
                    onPressed: () async {
                      var typedName = await Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) {
                          return CityScreen();
                        }),
                      );
                      if (typedName != null) {
                        var weatherData =
                            await weather.getCityWeather(typedName);
                        updateUI(weatherData);
                      }
                    },
                    child: Icon(
                      Icons.location_city,
                      size: 50.0,
                    ),
                  ),
                ],
              ),
              Padding(
                padding: EdgeInsets.only(left: 15.0),
                child: Row(
                  children: <Widget>[
                    Text(
                      '$temperature°',
                      style: kTempTextStyle,
                    ),
                    Text(
                      weatherIcon,
                      style: kConditionTextStyle,
                    ),
                  ],
                ),
              ),
              Padding(
                padding: EdgeInsets.only(right: 15.0),
                child: Text(
                  '$weatherMessage in $cityName',
                  textAlign: TextAlign.right,
                  style: kMessageTextStyle,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

도시검색화면 (city_screen.dart 파일)

  • Navigator.pop(context, cityName)
    전 페이지로 돌아갈 때, cityName변수를 전달한다.
  • onChanged: (value) { cityName = value; }
    TextField에 입력된 값이 cityName 속성에 전달된다.
import 'package:flutter/material.dart';
import 'package:clima/utilities/constants.dart';

class CityScreen extends StatefulWidget {
  
  _CityScreenState createState() => _CityScreenState();
}

class _CityScreenState extends State<CityScreen> {
  String cityName;
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: AssetImage('images/city_background.jpg'),
            fit: BoxFit.cover,
          ),
        ),
        constraints: BoxConstraints.expand(),
        child: SafeArea(
          child: Column(
            children: <Widget>[
              Align(
                alignment: Alignment.topLeft,
                child: FlatButton(
                  onPressed: () {
                    Navigator.pop(context);
                  },
                  child: Icon(
                    Icons.arrow_back_ios,
                    size: 50.0,
                  ),
                ),
              ),
              Container(
                padding: EdgeInsets.all(20.0),
                child: TextField(
                  style: TextStyle(color: Colors.black),
                  decoration: kTextFieldInputDecoration,
                  onChanged: (value) {
                    cityName = value;
                  },
                ),
              ),
              FlatButton(
                onPressed: () {
                  Navigator.pop(context, cityName);
                },
                child: Text(
                  'Get Weather',
                  style: kButtonTextStyle,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

constants.dart 파일

import 'package:flutter/material.dart';

const kTempTextStyle = TextStyle(
  fontFamily: 'Spartan MB',
  fontSize: 100.0,
);

const kMessageTextStyle = TextStyle(
  fontFamily: 'Spartan MB',
  fontSize: 60.0,
);

const kButtonTextStyle = TextStyle(
  fontSize: 30.0,
  fontFamily: 'Spartan MB',
);

const kConditionTextStyle = TextStyle(
  fontSize: 100.0,
);
const kTextFieldInputDecoration = InputDecoration(
  filled: true,
  fillColor: Colors.white,
  icon: Icon(
    Icons.location_city,
    color: Colors.white,
  ),
  hintText: 'Enter City Name',
  hintStyle: TextStyle(color: Colors.grey),
  border: OutlineInputBorder(
    borderRadius: BorderRadius.all(
      Radius.circular(10),
    ),
    borderSide: BorderSide.none,
  ),
);

실행

profile
Flutter 학습 일기

0개의 댓글