Popular Searches
Popular Course Categories
Popular Courses

Build a Flutter Weather App

Build a Flutter Weather App

Flutter Practical Projects

Build a Flutter Weather App

A Flutter Weather App is a practical project that demonstrates how to build a real-world application that retrieves weather information from an online API and displays it in a user-friendly interface. This project helps you understand Flutter UI development, HTTP requests, JSON parsing, asynchronous programming, API integration, state management, loading states, error handling, and responsive UI design.

In this project, the user can enter a city name, request weather information from a weather API, and display details such as temperature, weather condition, humidity, wind speed, and other useful information.


1. What is a Weather App?

A Weather App is an application that retrieves weather information from a weather service and presents the information to the user.

A typical weather application can display:

  • City name
  • Current temperature
  • Weather condition
  • Feels-like temperature
  • Humidity
  • Wind speed
  • Pressure
  • Visibility
  • Weather icon
  • Forecast information

2. Objectives of the Project

After completing this project, you will understand how to:

  • Create a Flutter Weather App.
  • Create a user-friendly weather interface.
  • Accept a city name using TextField.
  • Make HTTP GET requests.
  • Connect Flutter with a REST API.
  • Work with JSON data.
  • Create Dart model classes.
  • Use Future and async/await.
  • Handle loading states.
  • Handle API errors.
  • Display dynamic weather information.
  • Use setState() for simple state management.
  • Work with API keys securely at an appropriate application architecture level.
  • Create reusable widgets.

3. Technologies Used

TechnologyPurpose
FlutterBuilds the cross-platform user interface
DartProgramming language used by Flutter
Material DesignProvides Flutter UI components
HTTPMakes network requests to the weather API
JSONRepresents weather data returned by the API
FutureRepresents asynchronous operations
async/awaitHandles asynchronous API operations
StatefulWidgetManages changing application state

4. Basic Weather App Flow

User opens Weather App

        ↓

Weather screen appears

        ↓

User enters city name

        ↓

User presses Search

        ↓

Flutter sends HTTP request

        ↓

Weather API processes request

        ↓

API returns JSON response

        ↓

Flutter decodes JSON

        ↓

Weather model is created

        ↓

UI displays weather information

5. Understanding API Integration

An API allows the Flutter application to communicate with an external service. For a weather application, the Flutter app sends a request containing information such as the city name or geographic coordinates. The weather service processes the request and returns weather data.

Flutter App

    ↓

HTTP Request

    ↓

Weather API

    ↓

JSON Response

    ↓

Dart Model

    ↓

Flutter UI

Flutter's official documentation recommends the http package as a simple way to make HTTP requests. :contentReference[oaicite:0]{index=0}

6. Create a Flutter Project

Create a new Flutter project using the Flutter CLI.

flutter create weather_app

Move into the project directory:

cd weather_app

Run the application:

flutter run

7. Add the HTTP Package

The http package can be added using the Flutter command:

flutter pub add http

Import it into your Dart file:

import 'package:http/http.dart' as http;

The Flutter networking documentation uses this package for making HTTP requests and fetching data from the internet. :contentReference[oaicite:1]{index=1}

8. Android Internet Permission

When deploying an Android application that accesses the internet, the Android manifest needs the Internet permission.

This permission should be placed in the appropriate Android manifest file. Flutter's networking documentation specifically notes this requirement for Android network access. :contentReference[oaicite:2]{index=2}

9. Project Structure

weather_app/

├── android/

├── ios/

├── lib/

│   ├── main.dart

│   ├── models/

│   │   └── weather.dart

│   ├── services/

│   │   └── weather_service.dart

│   └── widgets/

│       └── weather_card.dart

├── test/

├── web/

├── pubspec.yaml

└── README.md

For a beginner project, the entire application can initially be created in main.dart. As the project grows, separating models, services, and widgets makes the code easier to maintain.

10. Create the Main Function

void main() {

  runApp(const WeatherApp());

}

The main() function is the starting point of the Dart application. The runApp() function places the root widget into the Flutter widget tree.

11. Create the Root Application

class WeatherApp extends StatelessWidget {

  const WeatherApp({super.key});

 

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      debugShowCheckedModeBanner: false,

      title: 'Weather App',

      theme: ThemeData(

        colorScheme: ColorScheme.fromSeed(

          seedColor: Colors.blue,

        ),

        useMaterial3: true,

      ),

      home: const WeatherPage(),

    );

  }

}

12. Why StatefulWidget is Useful

The weather screen needs to change when the user searches for a city. The application can move between states such as loading, successful data, and error.

A StatefulWidget is therefore useful for a simple implementation.

class WeatherPage extends StatefulWidget {

  const WeatherPage({super.key});

 

  @override

  State createState() => _WeatherPageState();

}

13. Create a Weather Model

Instead of working directly with a large JSON map throughout the UI, create a Dart class that represents the weather data required by the application.

class Weather {

  final String city;

  final double temperature;

  final double feelsLike;

  final int humidity;

  final double windSpeed;

  final String description;

 

  Weather({

    required this.city,

    required this.temperature,

    required this.feelsLike,

    required this.humidity,

    required this.windSpeed,

    required this.description,

  });

 

  factory Weather.fromJson(Map json) {

    return Weather(

      city: json['name'] ?? '',

      temperature:

          (json['main']['temp'] as num).toDouble(),

      feelsLike:

          (json['main']['feels_like'] as num).toDouble(),

      humidity: json['main']['humidity'] ?? 0,

      windSpeed:

          (json['wind']['speed'] as num).toDouble(),

      description:

          json['weather'][0]['description'] ?? '',

    );

  }

}

14. Understanding JSON

Weather APIs generally return structured data in JSON format. The JSON response may contain objects and arrays containing information about the city, temperature, humidity, wind, and weather conditions.

A simplified response might look like this:

{

  "name": "Mumbai",

  "main": {

    "temp": 28.5,

    "feels_like": 30.1,

    "humidity": 75

  },

  "weather": [

    {

      "description": "clear sky"

    }

  ],

  "wind": {

    "speed": 4.2

  }

}

15. JSON Parsing

Dart provides dart:convert for decoding JSON responses.

import 'dart:convert';

 

final data = jsonDecode(response.body);

The decoded JSON can then be converted into a Dart object.

final weather = Weather.fromJson(

  jsonDecode(response.body),

);

Flutter's networking examples demonstrate decoding an HTTP response body using jsonDecode() and converting the result into a Dart model. :contentReference[oaicite:3]{index=3}

16. Create a Weather Service

It is good practice to separate API communication from the UI. Create a service class responsible for requesting weather data.

import 'dart:convert';

import 'package:http/http.dart' as http;

 

class WeatherService {

  final String apiKey;

 

  WeatherService(this.apiKey);

 

  Future fetchWeather(String city) async {

    final url = Uri.parse(

      'https://api.example.com/weather'

      '?city=${Uri.encodeComponent(city)}'

      '&key=$apiKey',

    );

 

    final response = await http.get(url);

 

    if (response.statusCode == 200) {

      final data =

          jsonDecode(response.body) as Map;

 

      return Weather.fromJson(data);

    }

 

    throw Exception('Failed to load weather data');

  }

}

The exact endpoint, parameters, response fields, and authentication method depend on the weather API provider you choose.

17. HTTP GET Request

A GET request is commonly used to retrieve weather information.

final response = await http.get(

  Uri.parse(url),

);

The request returns a Future, because network communication happens asynchronously. :contentReference[oaicite:4]{index=4}

18. Understanding async and await

Network requests can take time. Flutter should not block the UI while waiting for a response.

Future fetchWeather(String city) async {

  final response = await http.get(

    Uri.parse(url),

  );

 

  // Process response

}

async allows the function to perform asynchronous work, while await waits for a Future to complete before continuing that function.

19. HTTP Status Codes

Status CodeMeaningPossible App Action
200SuccessParse and display weather
400Bad RequestCheck request parameters
401UnauthorizedCheck API credentials
404Not FoundShow city/data not found message
429Too Many RequestsHandle rate-limit condition
500Server ErrorShow server error message

20. Create TextEditingController

The user needs a way to enter the city name. TextEditingController can be used to read the input.

final TextEditingController _cityController =

    TextEditingController();

The entered city can be accessed using:

final city = _cityController.text.trim();

21. Dispose the Controller

When the State object is removed, the controller should be disposed.

@override

void dispose() {

  _cityController.dispose();

  super.dispose();

}

22. Create Weather App State Variables

Weather? _weather;

bool _isLoading = false;

String? _errorMessage;

These variables can represent three basic states:

StateMeaning
InitialNo weather has been searched yet
LoadingAPI request is in progress
SuccessWeather data was received
ErrorSomething went wrong while loading data

23. Create the Search Method

Future _searchWeather() async {

  final city = _cityController.text.trim();

 

  if (city.isEmpty) {

    setState(() {

      _errorMessage = 'Please enter a city name.';

    });

    return;

  }

 

  setState(() {

    _isLoading = true;

    _errorMessage = null;

  });

 

  try {

    final weather =

        await _weatherService.fetchWeather(city);

 

    setState(() {

      _weather = weather;

      _isLoading = false;

    });

  } catch (e) {

    setState(() {

      _isLoading = false;

      _errorMessage =

          'Unable to load weather information.';

    });

  }

}

24. Search Flow

Search Button

     ↓

Read city name

     ↓

Validate input

     ↓

Set loading = true

     ↓

Call Weather API

     ↓

Receive response

     ↓

Decode JSON

     ↓

Create Weather object

     ↓

Update state

     ↓

Display weather

25. Build the Search Field

TextField(

  controller: _cityController,

  textInputAction: TextInputAction.search,

  decoration: const InputDecoration(

    hintText: 'Enter city name',

    prefixIcon: Icon(Icons.location_city),

    border: OutlineInputBorder(),

  ),

  onSubmitted: (_) {

    _searchWeather();

  },

)

26. Create Search Button

ElevatedButton.icon(

  onPressed: _isLoading

      ? null

      : _searchWeather,

  icon: const Icon(Icons.search),

  label: const Text('Search'),

)

Disabling the button while loading helps prevent multiple requests from being sent at the same time.

27. Display Loading State

if (_isLoading)

  const Center(

    child: CircularProgressIndicator(),

  )

A loading indicator gives the user feedback while the API request is being processed.

28. Display Error State

if (_errorMessage != null)

  Text(

    _errorMessage!,

    style: const TextStyle(

      color: Colors.red,

    ),

  )

Error messages should be clear and useful rather than exposing raw technical exceptions to the user.

29. Display Weather Data

Column(

  children: [

    Text(

      _weather!.city,

      style: const TextStyle(

        fontSize: 28,

        fontWeight: FontWeight.bold,

      ),

    ),

    Text(

      '${_weather!.temperature.toStringAsFixed(1)}°C',

      style: const TextStyle(

        fontSize: 48,

        fontWeight: FontWeight.bold,

      ),

    ),

    Text(

      _weather!.description,

    ),

  ],

)

30. Weather Information Card

Card(

  child: Padding(

    padding: const EdgeInsets.all(20),

    child: Column(

      children: [

        Text(

          _weather!.city,

          style: const TextStyle(

            fontSize: 28,

            fontWeight: FontWeight.bold,

          ),

        ),

        const SizedBox(height: 12),

        Text(

          '${_weather!.temperature.toStringAsFixed(1)}°C',

          style: const TextStyle(

            fontSize: 50,

            fontWeight: FontWeight.bold,

          ),

        ),

        Text(

          _weather!.description,

          style: const TextStyle(

            fontSize: 18,

          ),

        ),

      ],

    ),

  ),

)

31. Display Humidity

WeatherInfoItem(

  icon: Icons.water_drop,

  label: 'Humidity',

  value: '${_weather!.humidity}%',

)

32. Display Wind Speed

WeatherInfoItem(

  icon: Icons.air,

  label: 'Wind',

  value: '${_weather!.windSpeed} m/s',

)

33. Reusable Weather Information Widget

class WeatherInfoItem extends StatelessWidget {

  final IconData icon;

  final String label;

  final String value;

 

  const WeatherInfoItem({

    super.key,

    required this.icon,

    required this.label,

    required this.value,

  });

 

  @override

  Widget build(BuildContext context) {

    return Column(

      children: [

        Icon(icon, size: 30),

        const SizedBox(height: 6),

        Text(label),

        const SizedBox(height: 4),

        Text(

          value,

          style: const TextStyle(

            fontWeight: FontWeight.bold,

          ),

        ),

      ],

    );

  }

}

34. Display Multiple Weather Details

Row(

  mainAxisAlignment:

      MainAxisAlignment.spaceAround,

  children: [

    WeatherInfoItem(

      icon: Icons.water_drop,

      label: 'Humidity',

      value: '${_weather!.humidity}%',

    ),

    WeatherInfoItem(

      icon: Icons.air,

      label: 'Wind',

      value: '${_weather!.windSpeed} m/s',

    ),

    WeatherInfoItem(

      icon: Icons.thermostat,

      label: 'Feels Like',

      value:

          '${_weather!.feelsLike.toStringAsFixed(1)}°C',

    ),

  ],

)

35. Complete Weather UI Structure

Scaffold

 ├── AppBar

 │    └── Text

 │

 └── Body

      └── SafeArea

           └── Padding

                └── Column

                     ├── TextField

                     ├── Search Button

                     ├── Loading Indicator

                     ├── Error Message

                     └── Weather Card

                          ├── City

                          ├── Temperature

                          ├── Description

                          └── Weather Details

                               ├── Humidity

                               ├── Wind

                               └── Feels Like

36. Complete Flutter Weather App Example

The following example demonstrates the application structure. Replace the example API endpoint and response fields with those provided by your selected weather API service.

import 'dart:convert';

 

import 'package:flutter/material.dart';

import 'package:http/http.dart' as http;

 

void main() {

  runApp(const WeatherApp());

}

 

class Weather {

  final String city;

  final double temperature;

  final double feelsLike;

  final int humidity;

  final double windSpeed;

  final String description;

 

  Weather({

    required this.city,

    required this.temperature,

    required this.feelsLike,

    required this.humidity,

    required this.windSpeed,

    required this.description,

  });

 

  factory Weather.fromJson(

    Map json,

  ) {

    return Weather(

      city: json['name'] ?? '',

      temperature:

          (json['main']['temp'] as num).toDouble(),

      feelsLike:

          (json['main']['feels_like'] as num).toDouble(),

      humidity: json['main']['humidity'] ?? 0,

      windSpeed:

          (json['wind']['speed'] as num).toDouble(),

      description:

          json['weather'][0]['description'] ?? '',

    );

  }

}

 

class WeatherService {

  final String apiKey;

 

  WeatherService(this.apiKey);

 

  Future fetchWeather(

    String city,

  ) async {

    final url = Uri.parse(

      'https://api.example.com/weather'

      '?city=${Uri.encodeComponent(city)}'

      '&key=$apiKey',

    );

 

    final response = await http.get(url);

 

    if (response.statusCode == 200) {

      final data =

          jsonDecode(response.body)

              as Map;

 

      return Weather.fromJson(data);

    }

 

    throw Exception(

      'Failed to load weather data',

    );

  }

}

 

class WeatherApp extends StatelessWidget {

  const WeatherApp({super.key});

 

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      debugShowCheckedModeBanner: false,

      title: 'Weather App',

      theme: ThemeData(

        colorScheme: ColorScheme.fromSeed(

          seedColor: Colors.blue,

        ),

        useMaterial3: true,

      ),

      home: const WeatherPage(),

    );

  }

}

 

class WeatherPage extends StatefulWidget {

  const WeatherPage({super.key});

 

  @override

  State createState() =>

      _WeatherPageState();

}

 

class _WeatherPageState

    extends State {

  final TextEditingController _cityController =

      TextEditingController();

 

  final WeatherService _weatherService =

      WeatherService('YOUR_API_KEY');

 

  Weather? _weather;

  bool _isLoading = false;

  String? _errorMessage;

 

  Future _searchWeather() async {

    final city = _cityController.text.trim();

 

    if (city.isEmpty) {

      setState(() {

        _errorMessage =

            'Please enter a city name.';

      });

      return;

    }

 

    setState(() {

      _isLoading = true;

      _errorMessage = null;

    });

 

    try {

      final weather =

          await _weatherService.fetchWeather(city);

 

      setState(() {

        _weather = weather;

        _isLoading = false;

      });

    } catch (e) {

      setState(() {

        _isLoading = false;

        _errorMessage =

            'Unable to load weather information.';

      });

    }

  }

 

  @override

  void dispose() {

    _cityController.dispose();

    super.dispose();

  }

 

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: const Text('Weather App'),

        centerTitle: true,

      ),

      body: SafeArea(

        child: Padding(

          padding: const EdgeInsets.all(16),

          child: Column(

            children: [

              TextField(

                controller: _cityController,

                textInputAction:

                    TextInputAction.search,

                decoration: const InputDecoration(

                  hintText: 'Enter city name',

                  prefixIcon:

                      Icon(Icons.location_city),

                  border: OutlineInputBorder(),

                ),

                onSubmitted: (_) {

                  _searchWeather();

                },

              ),

              const SizedBox(height: 12),

              SizedBox(

                width: double.infinity,

                child: ElevatedButton.icon(

                  onPressed: _isLoading

                      ? null

                      : _searchWeather,

                  icon: const Icon(Icons.search),

                  label: const Text('Search'),

                ),

              ),

              const SizedBox(height: 24),

              if (_isLoading)

                const CircularProgressIndicator(),

              if (_errorMessage != null)

                Padding(

                  padding:

                      const EdgeInsets.only(top: 16),

                  child: Text(

                    _errorMessage!,

                    textAlign: TextAlign.center,

                    style: const TextStyle(

                      color: Colors.red,

                    ),

                  ),

                ),

              if (_weather != null &&

                  !_isLoading)

                Expanded(

                  child: SingleChildScrollView(

                    child: Card(

                      child: Padding(

                        padding:

                            const EdgeInsets.all(20),

                        child: Column(

                          children: [

                            Text(

                              _weather!.city,

                              style: const TextStyle(

                                fontSize: 28,

                                fontWeight:

                                    FontWeight.bold,

                              ),

                            ),

                            const SizedBox(

                              height: 16,

                            ),

                            Text(

                              '${_weather!.temperature.toStringAsFixed(1)}°C',

                              style:

                                  const TextStyle(

                                fontSize: 48,

                                fontWeight:

                                    FontWeight.bold,

                              ),

                            ),

                            const SizedBox(

                              height: 8,

                            ),

                            Text(

                              _weather!.description,

                              style:

                                  const TextStyle(

                                fontSize: 18,

                              ),

                            ),

                            const SizedBox(

                              height: 24,

                            ),

                            Row(

                              mainAxisAlignment:

                                  MainAxisAlignment

                                      .spaceAround,

                              children: [

                                WeatherInfoItem(

                                  icon: Icons.water_drop,

                                  label: 'Humidity',

                                  value:

                                      '${_weather!.humidity}%',

                                ),

                                WeatherInfoItem(

                                  icon: Icons.air,

                                  label: 'Wind',

                                  value:

                                      '${_weather!.windSpeed} m/s',

                                ),

                                WeatherInfoItem(

                                  icon:

                                      Icons.thermostat,

                                  label: 'Feels Like',

                                  value:

                                      '${_weather!.feelsLike.toStringAsFixed(1)}°C',

                                ),

                              ],

                            ),

                          ],

                        ),

                      ),

                    ),

                  ),

                ),

            ],

          ),

        ),

      ),

    );

  }

}

 

class WeatherInfoItem

    extends StatelessWidget {

  final IconData icon;

  final String label;

  final String value;

 

  const WeatherInfoItem({

    super.key,

    required this.icon,

    required this.label,

    required this.value,

  });

 

  @override

  Widget build(BuildContext context) {

    return Column(

      children: [

        Icon(icon, size: 30),

        const SizedBox(height: 6),

        Text(label),

        const SizedBox(height: 4),

        Text(

          value,

          style: const TextStyle(

            fontWeight: FontWeight.bold,

          ),

        ),

      ],

    );

  }

}

37. Understanding the Complete Application

ComponentResponsibility
WeatherRepresents weather data
WeatherServiceCommunicates with the weather API
WeatherPageManages the weather screen
TextEditingControllerReads the city entered by the user
_searchWeather()Requests and processes weather information
WeatherInfoItemDisplays individual weather details
setState()Updates the UI after state changes

38. Loading, Success, and Error States

Initial State

     ↓

User searches

     ↓

Loading State

     ↓

 ┌───────────────┐

 │               │

Success         Error

 │               │

 ↓               ↓

Show Weather    Show Message

Handling these states makes the application easier to understand and provides useful feedback to the user.

39. Why API Calls Should Not Be Placed Directly in build()

The build() method can run many times. An API request placed directly inside build() could therefore be triggered repeatedly.

A better approach is to perform the request in an appropriate event or lifecycle method and store the resulting Future or state in a variable when needed. Flutter's official networking example specifically warns against repeatedly starting API calls from build(). :contentReference[oaicite:5]{index=5}

40. Using FutureBuilder

For simple applications, FutureBuilder can be used to build UI based on the state of a Future.

FutureBuilder(

  future: weatherFuture,

  builder: (context, snapshot) {

    if (snapshot.hasData) {

      return Text(

        snapshot.data!.city,

      );

    }

 

    if (snapshot.hasError) {

      return Text(

        'Error: ${snapshot.error}',

      );

    }

 

    return const CircularProgressIndicator();

  },

)

FutureBuilder can represent loading, success, and error states for asynchronous operations. :contentReference[oaicite:6]{index=6}

41. API Key Management

Many weather services require an API key. The exact authentication process depends on the selected provider.

Do not treat a secret credential embedded in a client application as fully private. For production systems, sensitive credentials should be handled according to the API provider's security recommendations and, where appropriate, protected behind a backend service.

For learning purposes, a placeholder such as the following can be used:

const String apiKey = 'YOUR_API_KEY';

42. Search by City

The simplest Weather App allows users to enter a city name.

TextField(

  controller: _cityController,

  decoration: const InputDecoration(

    hintText: 'Enter city name',

  ),

)

The city is then passed to the weather service:

final weather =

    await _weatherService.fetchWeather(city);

43. Search by Latitude and Longitude

More advanced weather applications can use geographic coordinates instead of a city name.

final latitude = 19.0760;

final longitude = 72.8777;

The coordinates can then be included in an API request according to the weather provider's API format.

44. Weather Icons

You can represent different weather conditions using Flutter icons or weather-specific image assets.

Icon(

  Icons.wb_sunny,

  size: 80,

)

A production application can map API weather-condition codes to appropriate icons.

ConditionExample Icon
ClearIcons.wb_sunny
CloudyIcons.cloud
RainIcons.water_drop
StormIcons.thunderstorm
NightIcons.nightlight

45. Responsive Weather UI

A weather application should work across different screen sizes. Avoid placing too many fixed-width elements on the screen.

LayoutBuilder(

  builder: (context, constraints) {

    if (constraints.maxWidth > 600) {

      return const Text(

        'Tablet/Desktop Layout',

      );

    }

 

    return const Text(

      'Mobile Layout',

    );

  },

)

46. Refresh Weather Data

A refresh button can allow users to request the latest weather information for the currently selected city.

IconButton(

  icon: const Icon(Icons.refresh),

  onPressed: _weather == null

      ? null

      : _searchWeather,

)

47. Pull-to-Refresh

For a more advanced interface, RefreshIndicator can be used with a scrollable widget.

RefreshIndicator(

  onRefresh: _searchWeather,

  child: ListView(

    children: [

      // Weather content

    ],

  ),

)

48. Weather Forecast Feature

The application can be extended from current weather to a multi-day forecast.

Current Weather

      ↓

Today

      ↓

Tomorrow

      ↓

Day 3

      ↓

Day 4

      ↓

Day 5

A forecast model could contain:

  • Date
  • Minimum temperature
  • Maximum temperature
  • Weather condition
  • Weather icon
  • Rain probability

49. Additional Weather Features

FeatureDescription
Current LocationShow weather for the device's location.
ForecastDisplay weather for upcoming days.
Search HistoryRemember previously searched cities.
FavoritesAllow users to save favorite cities.
Dark ModeProvide a dark application theme.
Weather AlertsDisplay important weather notifications when supported.
Unit SelectionAllow Celsius/Fahrenheit selection where supported.
AnimationsAdd smooth transitions between weather states.
Offline CacheShow previously loaded data when appropriate.

50. Location-Based Weather

An advanced application can request the device's location and use latitude and longitude to retrieve weather information.

Device Location

      ↓

Latitude + Longitude

      ↓

Weather API

      ↓

Weather Data

      ↓

Flutter UI

Location-based features require appropriate platform permissions and should clearly communicate to users why location access is needed.

51. Error Handling Best Practices

  • Check HTTP status codes.
  • Handle invalid city names.
  • Handle network failures.
  • Handle invalid or unexpected JSON responses.
  • Handle API authentication failures.
  • Handle rate limits.
  • Show user-friendly error messages.
  • Provide a retry option where appropriate.
  • Avoid displaying raw exceptions directly to users.

52. Network Timeout

Applications can use a timeout so a request does not wait indefinitely.

final response = await http

    .get(Uri.parse(url))

    .timeout(

      const Duration(seconds: 10),

    );

You can catch a timeout and display an appropriate message.

53. Testing the Weather App

Test the application with different scenarios:

  1. Open the application.
  2. Search for a valid city.
  3. Verify the temperature.
  4. Verify the weather description.
  5. Verify humidity.
  6. Verify wind speed.
  7. Search for another city.
  8. Try an empty search.
  9. Try an invalid city.
  10. Test the application without internet access.
  11. Test API authentication failure handling.
  12. Test loading indicators.
  13. Test different screen sizes.

54. Debugging API Requests

When a weather request does not work, inspect:

  • API URL
  • API key
  • Query parameters
  • HTTP method
  • HTTP status code
  • Response body
  • JSON structure
  • Internet permission
  • Network connection

Flutter DevTools includes a Network View that can help inspect HTTP, HTTPS, and WebSocket traffic in supported Flutter and Dart applications. :contentReference[oaicite:7]{index=7}

55. Common Mistakes

  • Forgetting to add the http package.
  • Forgetting Android Internet permission when required.
  • Using an incorrect API URL.
  • Using an invalid API key.
  • Incorrectly reading JSON fields.
  • Forgetting to use await.
  • Calling API methods repeatedly from build().
  • Not handling HTTP errors.
  • Not handling network exceptions.
  • Trying to display nullable weather data without checking it.
  • Not disposing TextEditingController.
  • Hard-coding API response assumptions without validating the response.

56. Best Practices

  • Separate API logic from UI code.
  • Create model classes for API responses.
  • Use async/await for asynchronous operations.
  • Handle loading, success, and error states.
  • Validate user input.
  • Keep API credentials out of source control.
  • Use reusable widgets for repeated UI elements.
  • Use meaningful class and variable names.
  • Keep network code testable.
  • Use appropriate error messages.
  • Test with both successful and failed network requests.

57. Suggested Project Architecture

lib/

├── main.dart

├── models/

│   └── weather.dart

├── services/

│   └── weather_service.dart

├── screens/

│   └── weather_page.dart

├── widgets/

│   ├── weather_card.dart

│   └── weather_info_item.dart

└── utils/

    └── constants.dart

58. Application Architecture Flow

                Flutter UI

                    ↓

              Weather Page

                    ↓

             Weather Service

                    ↓

               HTTP Client

                    ↓

              Weather API

                    ↓

               JSON Data

                    ↓

             Weather Model

                    ↓

              Flutter UI

59. Practical Project Improvements

After completing the basic Weather App, try implementing these improvements:

  1. Add current-location weather.
  2. Add a five-day forecast.
  3. Add search history.
  4. Add favorite cities.
  5. Add a dark theme.
  6. Add weather animations.
  7. Add refresh functionality.
  8. Add Celsius/Fahrenheit selection.
  9. Add weather icons based on API conditions.
  10. Add offline caching.
  11. Add unit tests for the weather model.
  12. Add tests for the API service using mocked responses.
  13. Separate the application into multiple files.

60. Interview Questions

Q1. What is an API?

An API is an interface that allows one software application to communicate with another software service.

Q2. Why is the http package used?

The http package provides convenient APIs for making HTTP requests such as GET, POST, PUT, and DELETE.

Q3. What is JSON?

JSON is a structured text format commonly used for exchanging data between applications and web services.

Q4. What does jsonDecode() do?

jsonDecode() converts a JSON-formatted string into Dart data structures such as maps and lists.

Q5. Why use async and await?

They make asynchronous operations such as network requests easier to write and understand without blocking the UI thread.

Q6. What is a Future?

A Future represents a value or error that will become available after an asynchronous operation completes.

Q7. Why should API calls not normally be placed directly in build()?

The build() method can be called repeatedly, so placing an API request directly inside it can cause repeated requests.

Q8. What is FutureBuilder?

FutureBuilder is a Flutter widget that builds UI based on the current state of a Future, such as loading, success, or error.

Q9. Why create a Weather model?

A model provides a structured representation of API data and makes the application easier to maintain.

Q10. How can a Weather App display data for different cities?

The application can accept a city name, include it in the API request, parse the returned data, and update the UI.

61. Learning Outcomes

By completing this project, you gain practical experience with Flutter networking and API-driven interfaces. You learn how to collect user input, make an asynchronous HTTP request, decode JSON, transform API data into Dart objects, update application state, and display the result in a responsive interface.

62. Final Project Flow

Create Flutter Project

        ↓

Add HTTP Package

        ↓

Create Weather Model

        ↓

Create Weather Service

        ↓

Configure API

        ↓

Create Search Field

        ↓

Accept City Name

        ↓

Send HTTP GET Request

        ↓

Receive JSON Response

        ↓

Decode JSON

        ↓

Create Weather Object

        ↓

Update Application State

        ↓

Display Weather Card

        ↓

Handle Loading and Errors

        ↓

Test Application

        ↓

Add Forecast and Advanced Features

63. Summary

  • A Weather App is an excellent project for learning Flutter API integration.
  • The http package can be used to make HTTP requests.
  • Weather data is commonly received in JSON format.
  • jsonDecode() can be used to decode JSON data.
  • Model classes make API data easier to work with.
  • async and await are useful for asynchronous network operations.
  • StatefulWidget and setState() can manage simple application state.
  • Loading and error states should be handled properly.
  • Weather API credentials should be handled securely.
  • A basic app can later be expanded with forecasts, location, favorites, caching, and notifications.


64. Learn Flutter

JustAcademy Flutter Training Course

Register for Flutter Course Demo

whatsapp