image

21 Jan 2026

9K

35K

Creating a Weather Forecast Widget with API in Flutter

Building dynamic and interactive applications often involves integrating external data sources. A common and practical example is a weather forecast widget, which fetches real-time weather data from an API and displays it within a Flutter application. This article will guide you through the process of creating such a widget, covering API integration, data modeling, and UI implementation.

Prerequisites

  • Basic understanding of Flutter and Dart.
  • Flutter SDK installed and configured.
  • A code editor (VS Code, Android Studio).

1. Choosing a Weather API and Obtaining an API Key

Several weather APIs are available, both free and paid. For this tutorial, we will use OpenWeatherMap, which offers a free tier suitable for development. Follow these steps:

  1. Go to OpenWeatherMap API and sign up for a free account.
  2. Once logged in, navigate to the "API keys" section to find your unique API key. Keep this key secure, as it's essential for making API requests.

OpenWeatherMap provides various endpoints, including current weather data and 5-day/3-hour forecasts. We'll focus on the latter for a comprehensive forecast widget.

2. Setting Up Your Flutter Project

First, create a new Flutter project:


flutter create weather_app
cd weather_app

Next, add the necessary dependencies to your pubspec.yaml file. We'll need http for making network requests and intl for date formatting.


dependencies:
  flutter:
    sdk: flutter
  http: ^1.1.0 # Or the latest stable version
  intl: ^0.18.1 # Or the latest stable version

After modifying pubspec.yaml, run flutter pub get to fetch the new packages.

3. Data Modeling

The weather API will return data in JSON format. To work with this data effectively in Dart, it's best to create a data model. This involves defining classes that mirror the structure of the JSON response. For OpenWeatherMap's 5-day/3-hour forecast, we'll simplify it to capture key information like date, temperature, and weather description.

Create a file named lib/models/weather_data.dart:


// lib/models/weather_data.dart

class Weather {
  final String main;
  final String description;
  final String icon;

  Weather({required this.main, required this.description, required this.icon});

  factory Weather.fromJson(Map<String, dynamic> json) {
    return Weather(
      main: json['main'],
      description: json['description'],
      icon: json['icon'],
    );
  }
}

class MainData {
  final double temp;
  final double tempMin;
  final double tempMax;
  final int humidity;

  MainData({
    required this.temp,
    required this.tempMin,
    required this.tempMax,
    required this.humidity,
  });

  factory MainData.fromJson(Map<String, dynamic> json) {
    return MainData(
      temp: (json['temp'] as num).toDouble(),
      tempMin: (json['temp_min'] as num).toDouble(),
      tempMax: (json['temp_max'] as num).toDouble(),
      humidity: json['humidity'],
    );
  }
}

class Forecast {
  final DateTime date;
  final MainData main;
  final List<Weather> weather;

  Forecast({
    required this.date,
    required this.main,
    required this.weather,
  });

  factory Forecast.fromJson(Map<String, dynamic> json) {
    var weatherList = json['weather'] as List;
    List<Weather> weatherItems =
        weatherList.map((i) => Weather.fromJson(i)).toList();

    return Forecast(
      date: DateTime.fromMillisecondsSinceEpoch(json['dt'] * 1000),
      main: MainData.fromJson(json['main']),
      weather: weatherItems,
    );
  }
}

class WeatherForecast {
  final List<Forecast> list;
  final String cityName;

  WeatherForecast({required this.list, required this.cityName});

  factory WeatherForecast.fromJson(Map<String, dynamic> json) {
    var list = json['list'] as List;
    List<Forecast> forecastList =
        list.map((i) => Forecast.fromJson(i)).toList();

    return WeatherForecast(
      list: forecastList,
      cityName: json['city']['name'],
    );
  }
}

4. Creating an API Service

Next, create a service class that will handle making the HTTP requests to OpenWeatherMap. This class will parse the JSON response into our Dart data model.

Create a file named lib/services/weather_service.dart:


// lib/services/weather_service.dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/weather_data.dart';

class WeatherService {
  final String apiKey;
  final String baseUrl = 'https://api.openweathermap.org/data/2.5';

  WeatherService({required this.apiKey});

  Future<WeatherForecast> fetchWeatherForecast(String city) async {
    final response = await http.get(Uri.parse(
        '$baseUrl/forecast?q=$city&appid=$apiKey&units=metric')); // units=metric for Celsius

    if (response.statusCode == 200) {
      return WeatherForecast.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to load weather forecast: ${response.statusCode}');
    }
  }

  String getWeatherIconUrl(String iconCode) {
    return 'http://openweathermap.org/img/wn/[email protected]';
  }
}

5. Building the Weather Widget UI

Now, let's integrate everything into our Flutter UI. We'll use a FutureBuilder to asynchronously fetch the weather data and display a loading indicator while the data is being fetched. We'll show the current weather and a list of forecast items.

Modify your lib/main.dart file:


// lib/main.dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'models/weather_data.dart';
import 'services/weather_service.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Weather App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const WeatherHomePage(),
    );
  }
}

class WeatherHomePage extends StatefulWidget {
  const WeatherHomePage({super.key});

  @override
  State<WeatherHomePage> createState() => _WeatherHomePageState();
}

class _WeatherHomePageState extends State<WeatherHomePage> {
  late Future<WeatherForecast> _weatherForecast;
  final String _cityName = 'London'; // Example city
  final WeatherService _weatherService = WeatherService(apiKey: 'YOUR_API_KEY'); // <-- Replace with your actual API Key

  @override
  void initState() {
    super.initState();
    _weatherForecast = _weatherService.fetchWeatherForecast(_cityName);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Weather in $_cityName'),
      ),
      body: FutureBuilder<WeatherForecast>(
        future: _weatherForecast,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (snapshot.hasData) {
            final weatherForecast = snapshot.data!;
            final currentForecast = weatherForecast.list.first; // Get current weather from the first entry

            return SingleChildScrollView(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  // Current Weather Section
                  _buildCurrentWeather(currentForecast, weatherForecast.cityName),
                  const SizedBox(height: 20),

                  // Forecast List
                  _buildForecastList(weatherForecast.list),
                ],
              ),
            );
          } else {
            return const Center(child: Text('No weather data available.'));
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _weatherForecast = _weatherService.fetchWeatherForecast(_cityName);
          });
        },
        child: const Icon(Icons.refresh),
      ),
    );
  }

  Widget _buildCurrentWeather(Forecast current, String cityName) {
    return Column(
      children: [
        Text(
          cityName,
          style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        Text(
          DateFormat('EEEE, MMM d, yyyy').format(current.date),
          style: const TextStyle(fontSize: 18, color: Colors.grey),
        ),
        const SizedBox(height: 16),
        Image.network(
          _weatherService.getWeatherIconUrl(current.weather.first.icon),
          width: 100,
          height: 100,
        ),
        Text(
          '${current.main.temp.round()}°C',
          style: const TextStyle(fontSize: 64, fontWeight: FontWeight.w300),
        ),
        Text(
          current.weather.first.description.toUpperCase(),
          style: const TextStyle(fontSize: 20, fontStyle: FontStyle.italic),
        ),
        const SizedBox(height: 16),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Min: ${current.main.tempMin.round()}°C', style: const TextStyle(fontSize: 16)),
            const SizedBox(width: 10),
            Text('Max: ${current.main.tempMax.round()}°C', style: const TextStyle(fontSize: 16)),
          ],
        ),
        Text('Humidity: ${current.main.humidity}%', style: const TextStyle(fontSize: 16)),
      ],
    );
  }

  Widget _buildForecastList(List<Forecast> forecasts) {
    // Group forecasts by day
    Map<String, List<Forecast>> dailyForecasts = {};
    for (var forecast in forecasts) {
      String dateKey = DateFormat('yyyy-MM-dd').format(forecast.date);
      if (!dailyForecasts.containsKey(dateKey)) {
        dailyForecasts[dateKey] = [];
      }
      dailyForecasts[dateKey]!.add(forecast);
    }

    // Display forecasts for the next few days (skipping the current day's multiple entries for simplicity)
    List<Widget> dailyWidgets = [];
    int daysToShow = 5; // Display up to 5 days
    int dayCount = 0;

    dailyForecasts.forEach((dateKey, dayEntries) {
      if (dayCount < daysToShow) {
        // Use the first entry of the day to represent the day's forecast
        // Or aggregate min/max temp for the day
        final representativeForecast = dayEntries.first; 

        // Skip the current day if we only want future forecasts
        if (DateFormat('yyyy-MM-dd').format(DateTime.now()) == dateKey && dayCount == 0) {
            // This is the current day, we already displayed its main info.
            // We can choose to skip it here or include hourly forecast.
            // For a simple daily summary, we will skip it and start from tomorrow.
            // If you want hourly breakdown for current day, you'd iterate through dayEntries here.
            dayCount++; // Count current day but don't add a summary card if only showing future days.
            return; 
        }

        dailyWidgets.add(
          Card(
            margin: const EdgeInsets.symmetric(vertical: 8.0),
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Row(
                children: [
                  Expanded(
                    flex: 2,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          DateFormat('EEE, MMM d').format(representativeForecast.date),
                          style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                        ),
                        Text(
                          representativeForecast.weather.first.description,
                          style: const TextStyle(fontSize: 14, color: Colors.grey),
                        ),
                      ],
                    ),
                  ),
                  Expanded(
                    flex: 1,
                    child: Image.network(
                      _weatherService.getWeatherIconUrl(representativeForecast.weather.first.icon),
                      width: 50,
                      height: 50,
                    ),
                  ),
                  Expanded(
                    flex: 1,
                    child: Text(
                      '${representativeForecast.main.temp.round()}°C',
                      style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w500),
                      textAlign: TextAlign.right,
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
        dayCount++;
      }
    });


    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Next 5 Days Forecast:',
          style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 10),
        ...dailyWidgets, // Spread operator to add all widgets from the list
      ],
    );
  }
}

Important: Remember to replace 'YOUR_API_KEY' with your actual OpenWeatherMap API key in lib/main.dart.

Running the Application

To run your Flutter weather app, connect a device or start an emulator and execute:


flutter run

Conclusion

You have successfully built a weather forecast widget in Flutter by integrating with a third-party API. This process involved setting up dependencies, defining data models, creating a service to fetch and parse data, and finally, designing the UI to display the information using a FutureBuilder for asynchronous data handling. This foundational knowledge can be extended to integrate with other APIs and build more complex dynamic features in your Flutter applications.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is