Search code examples
flutterapiopenweathermap

Flutter OpenWeatherMap api fetch forecast


So... I got this One Call Api which also includes forecast for 7 days. I managed to get display the current weather (temp, and icon) but how do I get the forecast for the next 4 days?

My API: https://api.openweathermap.org/data/2.5/onecall?lat=lat&lon=lon&exclude=minutely,hourly,alerts&cnt=4&appid={MyKey}&units=metric

My model:

class CurrentWeatherModel {
      final WeatherInfo weatherInfo;
      final double temp;
      const CurrentWeatherModel({
        required this.weatherInfo, required this.temp
      });
    
      factory CurrentWeatherModel.fromJson(json){
        return CurrentWeatherModel(
            weatherInfo: WeatherInfo.fromJson(json['weather'][0]),
          temp: json['temp'],
        );
      }
    }
    
    class HourlyWeatherModel {
      final double temp;
      final WeatherInfo weatherInfo;
      const HourlyWeatherModel(
          {
            required this.temp,
            required this.weatherInfo
          }
          );
    
      factory HourlyWeatherModel.fromJson(json){
        return HourlyWeatherModel(
            temp: json['temp'],
            weatherInfo: WeatherInfo.fromJson(json['weather'][0])
        );
      }
    }
    
    class DailyWeatherInfoModel {
      final TempModel dailyTemp;
      final WeatherInfo weatherInfo;
      final DateTime date;
      final int dt;
    
      const DailyWeatherInfoModel(
          {
            required this.dailyTemp,
            required this.weatherInfo,
            required this.date,
            required this.dt,
          }
          );
    
      factory DailyWeatherInfoModel.fromJson(json){
        return DailyWeatherInfoModel(
          dailyTemp: TempModel.fromJson(json['temp']),
          dt: json['dt'],
          date: DateTime.fromMillisecondsSinceEpoch(json['dt'] * 1000,
              isUtc: true),
            weatherInfo: WeatherInfo.fromJson(json['weather'][0]),
        );
      }
    }
    
    class TempModel {
      final double day;
      final double min;
      final double max;
      const TempModel(
          {
            required this.day,
            required this.min,
            required this.max
          }
          );
      factory TempModel.fromJson(json){
        return TempModel(
            day: json['day'],
            min: json['min'],
            max: json['max']
        );
      }
    }
    
    class WeatherInfo {
      final String? description;
      final String? icon;
      WeatherInfo({this.description, this.icon});
      factory WeatherInfo.fromJson(Map<String, dynamic> json) {
        final description = json['description'];
        final icon = json['icon'];
        return WeatherInfo(description: description, icon: icon);
      }
    }
    
    class WeatherForecastResponse {
    
      final CurrentWeatherModel current;
      final HourlyWeatherModel hourly;
      final DailyWeatherInfoModel daily;
    
      String get iconCurrentWeatherUrl{
        return 'https://merakiapp.be/wp-content/uploads/2022/04/${current.weatherInfo.icon}.png';
      }
    
      String get iconDailyWeatherUrl{
        return 'https://merakiapp.be/wp-content/uploads/2022/04/${daily.weatherInfo.icon}.png';
      }
    
      const  WeatherForecastResponse(
          {
            required this.current,
            required this.daily,
            required this.hourly
          }
          );
    
      factory WeatherForecastResponse.fromJson(json){
        return WeatherForecastResponse(
            current: CurrentWeatherModel.fromJson(json['current']),
            hourly: HourlyWeatherModel.fromJson(json['hourly'][0]),
            daily: DailyWeatherInfoModel.fromJson(json['daily'][0])
        );
      }
    }

My Response:

Future<WeatherForecastResponse> getForecast(double lat, double lon) async {
    try {
      String api = 'https://api.openweathermap.org/data/2.5/onecall';
      String appId = 'MyAPIKey';
      String units = 'metric';
      String cnt = '4';
      String url = '$api?lat=$lat&lon=$lon&cnt=$cnt&appid=$appId&units=$units';

      final response = await http.get(Uri.parse(url));
      final json = jsonDecode(response.body);
      return WeatherForecastResponse.fromJson(json);
    } catch (e) {
      rethrow;
    }
  }

Respons object:

void _currentWeather() async {
    await FirebaseFirestore.instance.collection('Users').doc(currentUser).get().then((value) => _userLocation = value.data()!['Location']);
    setState(() {
      lat = _userLocation.latitude;
      lon = _userLocation.longitude;
    });
    final response = await _dataWeatherService.getForecast(lat!, lon!);
    setState(() {
      temp = response.current.temp.round();
      weather = response.current.weatherInfo.description;
      icon = response.iconCurrentWeatherUrl;
    });
  }

  void _dailyForecast() async {
    final response = await _dataWeatherService.getForecast(lat!, lon!);
    setState(() {
      tempDaily = response.daily.dailyTemp.day.round();
      weatherDaily = response.daily.weatherInfo.description;
      iconDaily = response.iconDailyWeatherUrl;
      date = response.daily.date;
    });
}

JSON from OneCall API:

{
  "lat": 33.44,
  "lon": -94.04,
  "timezone": "America/Chicago",
  "timezone_offset": -21600,
  "current": {
    "dt": 1618317040,
    "sunrise": 1618282134,
    "sunset": 1618333901,
    "temp": 284.07,
    "feels_like": 282.84,
    "pressure": 1019,
    "humidity": 62,
    "dew_point": 277.08,
    "uvi": 0.89,
    "clouds": 0,
    "visibility": 10000,
    "wind_speed": 6,
    "wind_deg": 300,
    "weather": [
      {
        "id": 500,
        "main": "Rain",
        "description": "light rain",
        "icon": "10d"
      }
    ],
    "rain": {
      "1h": 0.21
    }
  },
    "minutely": [
    {
      "dt": 1618317060,
      "precipitation": 0.205
    },
    ...
  },
    "hourly": [
    {
      "dt": 1618315200,
      "temp": 282.58,
      "feels_like": 280.4,
      "pressure": 1019,
      "humidity": 68,
      "dew_point": 276.98,
      "uvi": 1.4,
      "clouds": 19,
      "visibility": 306,
      "wind_speed": 4.12,
      "wind_deg": 296,
      "wind_gust": 7.33,
      "weather": [
        {
          "id": 801,
          "main": "Clouds",
          "description": "few clouds",
          "icon": "02d"
        }
      ],
      "pop": 0
    },
    ...
  }
    "daily": [
    {
      "dt": 1618308000,
      "sunrise": 1618282134,
      "sunset": 1618333901,
      "moonrise": 1618284960,
      "moonset": 1618339740,
      "moon_phase": 0.04,
      "temp": {
        "day": 279.79,
        "min": 275.09,
        "max": 284.07,
        "night": 275.09,
        "eve": 279.21,
        "morn": 278.49
      },
      "feels_like": {
        "day": 277.59,
        "night": 276.27,
        "eve": 276.49,
        "morn": 276.27
      },
      "pressure": 1020,
      "humidity": 81,
      "dew_point": 276.77,
      "wind_speed": 3.06,
      "wind_deg": 294,
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10d"
        }
      ],
      "clouds": 56,
      "pop": 0.2,
      "rain": 0.62,
      "uvi": 1.93
    },
    ...
    },
    "alerts": [
    {
      "sender_name": "NWS Tulsa",
      "event": "Heat Advisory",
      "start": 1597341600,
      "end": 1597366800,
      "description": "...HEAT ADVISORY REMAINS IN EFFECT FROM 1 PM THIS AFTERNOON TO\n8 PM CDT THIS EVENING...\n* WHAT...Heat index values of 105 to 109 degrees expected.\n* WHERE...Creek, Okfuskee, Okmulgee, McIntosh, Pittsburg,\nLatimer, Pushmataha, and Choctaw Counties.\n* WHEN...From 1 PM to 8 PM CDT Thursday.\n* IMPACTS...The combination of hot temperatures and high\nhumidity will combine to create a dangerous situation in which\nheat illnesses are possible.",
      "tags": [
        "Extreme temperature value"
        ]
    },
    ...
  ]

Solution

  • I would change your WeatherForecastResponse to have a list of DailyWeatherInfoModel vs a single model.

    
    class WeatherForecastResponse {
      final CurrentWeatherModel current;
      final HourlyWeatherModel hourly;
      final List<DailyWeatherInfoModel> daily;
    ...
    }
    

    Then you parse a full list in the fromJson constructor

      factory WeatherForecastResponse.fromJson(json) {
        final dailyResponse = json['daily'] as List;
        final dailyForecastList = <DailyWeatherInfoModel>[];
        for (final day in dailyResponse) {
          dailyForecastList.add(DailyWeatherInfoModel.fromJson(day));
        }
        return WeatherForecastResponse(
            current: CurrentWeatherModel.fromJson(json['current']),
            hourly: HourlyWeatherModel.fromJson(json['hourly'][0]),
            daily: dailyForecastList);
      }
    

    I would also just put the icon url in each daily/hourly model. This way you have a fully self contained list of DailyWeatherInfoModel with everything you need.

    class DailyWeatherInfoModel {
      final TempModel dailyTemp;
      final WeatherInfo weatherInfo;
      final DateTime date;
      final int dt;
      final String iconUrl; // adding this
    
      const DailyWeatherInfoModel({
        required this.dailyTemp,
        required this.weatherInfo,
        required this.date,
        required this.dt,
        required this.iconUrl,
      });
    
      factory DailyWeatherInfoModel.fromJson(json) {
        final weatherInfo = WeatherInfo.fromJson(json['weather'][0]);
        final iconUrl =
            'https://merakiapp.be/wp-content/uploads/2022/04/${weatherInfo.icon}.png'; // init iconUrl here
    
        return DailyWeatherInfoModel(
          dailyTemp: TempModel.fromJson(json['temp']),
          dt: json['dt'],
          date: DateTime.fromMillisecondsSinceEpoch(json['dt'] * 1000, isUtc: true),
          weatherInfo: weatherInfo,
          iconUrl: iconUrl,
        );
      }
    }
    
    

    You can apply the same concept to the hourly forecast as well.