Programming/Flutter

플러터(Flutter) 스터디 5주차

알로그 2021. 11. 7. 10:54
반응형

Udemy 플러터 강의 5주차

 ㄴ Section 13. Clima - Powering Your Flutter App with Live Web Data

https://www.udemy.com/course/flutter-bootcamp-with-dart/

 

이번 강의에서는 geolocator package를 이용해서 위도, 경도에 대한 위치 정보를 가져온 뒤, API를 이용하여 해당 위치(또는 도시)의 날씨 정보를 가져오는 앱을 구축하는 과정에 대해 설명한다.

 

강의내용요약

Flutter

  • geolocator Package
  • Stateful Widget Lifecycle
  • http Package
  • Passing Data Backwards Through the Navigation Stack

Dart

  • Futures, Async, Await
  • Exception Handling, Null Aware Operations
  • dynamic type

 

 

소스코드

location.dart

  • geolocator package 사용
  • 7.x 버전부터는 sdk 31로 교체 필요함
  • async, await 사용
  • Future 사용
  • LocationAccuracy.low 옵션을 통해 정확도 설정 가능
import 'package:geolocator/geolocator.dart';

class Location {
  late double latitude;
  late double longitude;

  Future<void> getCurrentLocation() async {
    try {
      // Geolocator API로 위도, 경도 호출
      Position position = await Geolocator.getCurrentPosition(
          desiredAccuracy: LocationAccuracy.low,
          forceAndroidLocationManager: true);

      latitude = position.latitude;
      longitude = position.longitude;

    } catch (e) {
      print(e);
    }
  }
}

 

networking.dart

  • http Package 사용 (날씨 정보를 가져오는 API를 호출하기 위함)
  • http Pacakge는 클래스 형태로 구현되어 있지 않기 때문에 가독성 향상을 위해 as http 명시
  • dart:convert로 json 파싱
import 'package:http/http.dart' as http;
import 'dart:convert';

class NetworkHelper {
  NetworkHelper(this.url);

  final String url;

  Future<void> getData() async {
    // get method에 string인 url을 Uri.parse를 통해 변환 후 전달
    http.Response response = await http.get(Uri.parse(url));

    // response 정상인 경우 200 반환
    if (response.statusCode == 200) {
      String data = response.body;
      return jsonDecode(data);
    } else {
      print(response.statusCode);
    }
  }
}

 

weather.dart

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

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

class WeatherModel {
  // 도시 호출하는 API 이용
  Future<dynamic> getCityWeather(String cityName) async {
    NetworkHelper networkHelper = NetworkHelper(
        '$openWeatherMapURL?q=$cityName&appid=$apiKey&units=metric');

    var weatherData = await networkHelper.getData();

    return weatherData;
  }

  // 위도, 경도 호출하는 API 이용
  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';
    }
  }
}

 

loading_screen.dart

  • Widget Lifecycle: initState -> build -> deactivate
  • spinKitDoubleBounce로 로딩화면 표시
import 'package:clima_flutter_flearner/screens/location_screen.dart';
import 'package:flutter/material.dart';
import 'location_screen.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:clima_flutter_flearner/services/weather.dart';

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

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

  void getLocationData() async {
    // 클래스 초기화와 메소드 호출 동시에 가능
    var weatherData = await WeatherModel().getLocationWeather();

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

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

 

location_screen.dart

  • widget을 통해 LocationScreen 변수 접근 가능
  • city_screen에서 pop한 값을 typedName에서 받아옴
import 'package:clima_flutter_flearner/screens/city_screen.dart';
import 'package:flutter/material.dart';
import 'package:clima_flutter_flearner/utilities/constants.dart';
import 'package:clima_flutter_flearner/services/weather.dart';
import 'city_screen.dart';

class LocationScreen extends StatefulWidget {
  LocationScreen({this.locationWeather});
  final locationWeather;

  @override
  _LocationScreenState createState() => _LocationScreenState();
}

class _LocationScreenState extends State<LocationScreen> {
  WeatherModel weather = WeatherModel();
  late int temperature;
  late String weatherIcon;
  late String cityName;
  late String weatherMessage;

  @override
  void initState() {
    super.initState();

    // LocationScreen의 값을 widget을 통해 접근 가능
    updateUI(widget.locationWeather);
  }

  void updateUI(dynamic weatherData) {
    setState(() {
      if (weatherData == null) {
        temperature = 0;
        weatherIcon = 'Error';
        weatherMessage = 'Unable to get weather data';
        cityName = '';
        return;
      }

      temperature = weatherData['main']['temp'].toInt();
      var condition = weatherData['weather'][0]['id'];
      weatherIcon = weather.getWeatherIcon(condition);
      weatherMessage = weather.getMessage(temperature);
      cityName = weatherData['name'];

      print(cityName);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: const AssetImage('images/location_background.jpg'),
            fit: BoxFit.cover,
            colorFilter: ColorFilter.mode(
                Colors.white.withOpacity(0.8), BlendMode.dstATop),
          ),
        ),
        constraints: const BoxConstraints.expand(),
        child: SafeArea(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  TextButton(
                  // 현재 위치에 대한 날씨 정보 가져오는 버튼
                    onPressed: () async {
                      var weatherData = await weather.getLocationWeather();
                      updateUI(weatherData);
                    },
                    child: const Icon(
                      Icons.near_me,
                      size: 50.0,
                    ),
                  ),
                  TextButton(
                  // 도시 입력하는 페이지로 전달, typedName은 해당 페이지에서 pop 값을 받음
                    onPressed: () async {
                      var typedName = await Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) => CityScreen()));
                      if (typedName != null) {
                        var weatherData =
                            await weather.getCityWeather(typedName);
                        updateUI(weatherData);
                      }
                    },
                    child: const 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으로 도시 이름 전달
import 'package:flutter/material.dart';
import 'package:clima_flutter_flearner/utilities/constants.dart';

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

class _CityScreenState extends State<CityScreen> {
  String? cityName;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: const BoxDecoration(
          image: DecorationImage(
            image: AssetImage('images/city_background.jpg'),
            fit: BoxFit.cover,
          ),
        ),
        constraints: const BoxConstraints.expand(),
        child: SafeArea(
          child: Column(
            children: <Widget>[
              Align(
                alignment: Alignment.topLeft,
                child: TextButton(
                  onPressed: () {
                    // 뒤로 버튼 눌렀을 때
                    Navigator.pop(context);
                  },
                  child: const Icon(
                    Icons.arrow_back_ios,
                    size: 50.0,
                  ),
                ),
              ),
              Container(
                padding: const EdgeInsets.all(20.0),
                child: TextField(
                  style: TextStyle(
                    color: Colors.black,
                  ),
                  decoration: kTextFieldInputDecoration,
                  onChanged: (value) {
                    // 값이 입력될 때 cityName에 입력된 값 전달
                    cityName = value;
                  },
                ),
              ),
              TextButton(
                onPressed: () {
                  // 도시 이름까지 같이 전달
                  Navigator.pop(context, cityName);
                  print(cityName);
                },
                child: Text(
                  'Get Weather',
                  style: kButtonTextStyle,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
반응형

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

플러터(Flutter) 스터디 6주차  (0) 2021.11.13
플러터 스터디 3주차  (0) 2021.10.30
플러터(Flutter) 스터디 2주차  (0) 2021.10.23
플러터(Flutter) 스터디 1주차  (0) 2021.10.04