- 제공할 서비스
현재 디바이스 위치의 날씨정보 제공
다른 도시검색 및 검색한 도시의 날씨정보 제공
현재위치 초기화
- 해야할것
디바이스의 위치정보 가져오기
날씨데이터 가져오기(open weathermap사이트 API이용)
화면 및 기능 구현 & 페이지간 데이터 전달
geolcator 패키지를 통해 디바이스의 위치좌표를 구해야한다.
https://pub.dev/packages/geolocator
pubspec.yami
파일dependencies:
geolocator: ^8.0.0
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" />
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);
}
}
}
플러터 문서에 외부 데이터를 가져오는 방법이 나와있다.
https://docs.flutter.dev/cookbook/networking/fetch-data
http 패키지를 불러옴으로써 외부 사이트의 API를 통해 데이터를 가져올 수 있다.
https://pub.dev/packages/http
pubspec.yami
파일dependencies:
http: <latest_version>
import 'package:http/http.dart' as http;
AndroidManifest.xml
파일<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
dart:convert
importimport 'dart:convert';
'dart:convert'를 통해 가져온 JSON데이터의 parsing이 가능하다.
openweathermap 사이트의 날씨데이터에 접근하는 API
https://openweathermap.org/current#one
API를 이용하기 위해 API key가 필요하다.
가입후 API key를 발급받자.
https://home.openweathermap.org/api_keys
- 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);
}
}
}
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';
}
}
}
테마지정 및 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(),
);
}
}
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
}
}
- 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,
),
),
],
),
),
),
);
}
}
- 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,
),
),
],
),
),
),
);
}
}
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,
),
);