<h1 id="build-a-simple-weather-app-using-flutter-and-openweathermap-api">Build a Simple Weather App Using Flutter and OpenWeatherMap API</h1> <h2 id="image-recommendation-add-a-screenshot-of-the-completed-weather-app-showing-weather-information-for-a-city.the-image-should-display-temperature-weather-condition-sunnyrainycloudy-and-the-forecast-view.include-a-professional-mobile-device-mockup-to-showcase-the-final-ui-design.file-name-weather_app_screenshot.png"><!-- Image recommendation: Add a screenshot of the completed weather app showing weather information for a city. The image should display temperature, weather condition (sunny/rainy/cloudy), and the forecast view. Include a professional mobile device mockup to showcase the final UI design. File name: weather_app_screenshot.png</h2> <p>In this tutorial, we'll build a simple but elegant weather app using Flutter that fetches real-time weather data from the OpenWeatherMap API. You'll learn how to make API calls, parse JSON data, and create a beautiful UI to display weather information.</p> <h2 id="prerequisites">Prerequisites</h2> <ul> <li>Flutter SDK installed on your machine</li> <li>OpenWeatherMap API key (sign up at <a href="https://openweathermap.org/api">OpenWeatherMap</a>)</li> <li>Basic knowledge of Flutter and Dart</li> </ul> <h2 id="project-setup">Project Setup</h2> <p>First, create a new Flutter project:</p> <pre>flutter create weather_app cd weather_app </pre> <p>Next, open your <code>pubspec.yaml</code> file and add the following dependencies:</p> <pre>dependencies: flutter: sdk: flutter http: ^1.1.0 # For API requests geolocator: ^10.0.0 # For getting device location intl: ^0.18.1 # For date formatting flutter_spinkit: ^5.2.0 # For loading indicators </pre> <p>Run <code>flutter pub get</code> to install the dependencies.</p> <h2 id="setting-up-the-api-service">Setting Up the API Service</h2> <p>Create a new file called <code>weather_service.dart</code> in the <code>lib</code> folder:</p> <pre>import 'dart:convert'; import 'package:http/http.dart' as http;
class WeatherService { final String apiKey; final String baseUrl = 'https://api.openweathermap.org/data/2.5';
WeatherService();
Future<Map<String, dynamic>> getWeatherByCity(String city) async { final response = await http.get( Uri.parse('$baseUrl/weather?q=$city&appid=$apiKey&units=metric'), );
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception(&#39;Failed to load weather data: ${response.statusCode}&#39;);
}
}
Future<Map<String, dynamic>> getWeatherByLocation( double latitude, double longitude) async { final response = await http.get( Uri.parse( '$baseUrl/weather?lat=$latitude&lon=$longitude&appid=$apiKey&units=metric'), );
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception(&#39;Failed to load weather data: ${response.statusCode}&#39;);
}
}
Future<Map<String, dynamic>> getForecast( double latitude, double longitude) async { final response = await http.get( Uri.parse( '$baseUrl/forecast?lat=$latitude&lon=$longitude&appid=$apiKey&units=metric'), );
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception(&#39;Failed to load forecast data: ${response.statusCode}&#39;);
}
} } </pre> <h2 id="creating-the-weather-model">Creating the Weather Model</h2> <p>Create a new file called <code>weather_model.dart</code>:</p> <pre>class Weather { final String cityName; final double temperature; final String description; final int humidity; final double windSpeed; final String icon; final DateTime date;
Weather({ required this.cityName, required this.temperature, required this.description, required this.humidity, required this.windSpeed, required this.icon, required this.date, });
factory Weather.fromJson(Map<String, dynamic> json) { return Weather( cityName: json['name'], temperature: json['main']['temp'].toDouble(), description: json['weather'][0]['description'], humidity: json['main']['humidity'], windSpeed: json['wind']['speed'].toDouble(), icon: json['weather'][0]['icon'], date: DateTime.fromMillisecondsSinceEpoch(json['dt'] * 1000), ); } }
class ForecastDay { final DateTime date; final double maxTemp; final double minTemp; final String icon; final String description;
ForecastDay({ required this.date, required this.maxTemp, required this.minTemp, required this.icon, required this.description, });
factory ForecastDay.fromJson(Map<String, dynamic> json) { return ForecastDay( date: DateTime.fromMillisecondsSinceEpoch(json['dt'] * 1000), maxTemp: json['main']['temp_max'].toDouble(), minTemp: json['main']['temp_min'].toDouble(), icon: json['weather'][0]['icon'], description: json['weather'][0]['description'], ); } } </pre> <h2 id="building-the-ui">Building the UI</h2> <h3 id="home-screen">Home Screen</h3> <p>Create a file called <code>weather_screen.dart</code>:</p> <pre>import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:geolocator/geolocator.dart'; import 'package:intl/intl.dart'; import 'weather_model.dart'; import 'weather_service.dart';
class WeatherScreen extends StatefulWidget { final String apiKey;
const WeatherScreen({Key? key, required this.apiKey}) : super(key: key);
@override _WeatherScreenState createState() => _WeatherScreenState(); }
class _WeatherScreenState extends State<WeatherScreen> { final TextEditingController _cityController = TextEditingController(); Weather? _currentWeather; List<ForecastDay> _forecast = []; bool _isLoading = false; String _errorMessage = '';
late WeatherService _weatherService;
@override void initState() { super.initState(); _weatherService = WeatherService(apiKey: widget.apiKey); _getCurrentLocation(); }
Future<void> _getCurrentLocation() async { setState(() );
try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
setState(() {
_errorMessage = &#39;Location services are disabled.&#39;;
_isLoading = false;
});
return;
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
setState(() {
_errorMessage = &#39;Location permissions are denied.&#39;;
_isLoading = false;
});
return;
}
}
if (permission == LocationPermission.deniedForever) {
setState(() {
_errorMessage = &#39;Location permissions are permanently denied.&#39;;
_isLoading = false;
});
return;
}
final position = await Geolocator.getCurrentPosition();
await _getWeatherByLocation(position.latitude, position.longitude);
await _getForecast(position.latitude, position.longitude);
} catch (e) {
setState(() {
_errorMessage = &#39;Error getting location: $e&#39;;
_isLoading = false;
});
}
}
Future<void> _getWeatherByCity(String city) async { setState(() );
try {
final data = await _weatherService.getWeatherByCity(city);
setState(() {
_currentWeather = Weather.fromJson(data);
_isLoading = false;
});
// Get forecast data based on coordinates from city search
final lat = data[&#39;coord&#39;][&#39;lat&#39;];
final lon = data[&#39;coord&#39;][&#39;lon&#39;];
await _getForecast(lat, lon);
} catch (e) {
setState(() {
_errorMessage = &#39;Error fetching weather: $e&#39;;
_isLoading = false;
});
}
}
Future<void> _getWeatherByLocation(double lat, double lon) async { try { final data = await _weatherService.getWeatherByLocation(lat, lon); setState(() ); } catch (e) { setState(() { _errorMessage = 'Error fetching weather: $e'; _isLoading = false; }); } }
Future<void> _getForecast(double lat, double lon) async { try { final data = await _weatherService.getForecast(lat, lon); List<ForecastDay> forecast = [];
// Group forecast by day (every 24 hours)
int i = 0;
while (i &lt; data[&#39;list&#39;].length) {
forecast.add(ForecastDay.fromJson(data[&#39;list&#39;][i]));
i += 8; // Skip to next day (3-hour intervals, 8 times = 24 hours)
}
setState(() {
_forecast = forecast;
});
} catch (e) {
print(&#39;Error fetching forecast: $e&#39;);
}
}
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Weather App'), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _getCurrentLocation, ), ], ), body: _isLoading ? const Center( child: SpinKitPulse( color: Colors.blue, size: 50.0, ), ) : _errorMessage.isNotEmpty ? Center(child: Text(_errorMessage)) : _buildWeatherContent(), ); }
Widget _buildWeatherContent() { if (_currentWeather == null) { return Center(child: Text('No weather data available')); }
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildSearchBar(),
const SizedBox(height: 20),
_buildCurrentWeather(),
const SizedBox(height: 20),
_buildForecast(),
],
),
);
}
Widget _buildSearchBar() { return Card( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Row( children: [ Expanded( child: TextField( controller: _cityController, decoration: const InputDecoration( hintText: 'Search city', border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 16), ), onSubmitted: (value) { if (value.isNotEmpty) { _getWeatherByCity(value); } }, ), ), IconButton( icon: const Icon(Icons.search), onPressed: () { if (_cityController.text.isNotEmpty) { _getWeatherByCity(_cityController.text); } }, ), ], ), ), ); }
Widget _buildCurrentWeather() { final weather = _currentWeather!; return Card( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Text( weather.cityName, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), Text( DateFormat('EEEE, MMM d').format(weather.date), style: TextStyle(fontSize: 16, color: Colors.grey[700]), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Image.network( 'https://openweathermap.org/img/wn/$@2x.png', width: 80, height: 80, ), Text( '${weather.temperature.toStringAsFixed(1)}°C', style: const TextStyle( fontSize: 32, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 8), Text( weather.description.toUpperCase(), style: const TextStyle(fontSize: 16), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildWeatherDetail( Icons.water_drop, '$%', 'Humidity'), _buildWeatherDetail(Icons.air, '$ m/s', 'Wind Speed'), ], ), ], ), ), ); }
Widget _buildWeatherDetail(IconData icon, String value, String label) { return Column( children: [ Icon(icon, color: Colors.blue), const SizedBox(height: 8), Text( value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), Text( label, style: TextStyle(fontSize: 14, color: Colors.grey[700]), ), ], ); }
Widget _buildForecast() { if (_forecast.isEmpty) { return const SizedBox.shrink(); }
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(left: 8.0, bottom: 8.0),
child: Text(
&#39;5-Day Forecast&#39;,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
SizedBox(
height: 140,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _forecast.length,
itemBuilder: (context, index) {
final day = _forecast[index];
return Card(
elevation: 3,
margin: const EdgeInsets.symmetric(horizontal: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
child: Container(
width: 100,
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
DateFormat(&#39;E&#39;).format(day.date),
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Image.network(
&#39;https://openweathermap.org/img/wn/${day.icon}.png&#39;,
width: 40,
height: 40,
),
const SizedBox(height: 4),
Text(
&#39;${day.maxTemp.toStringAsFixed(0)}&#176;&#39;,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(
&#39;${day.minTemp.toStringAsFixed(0)}&#176;&#39;,
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
);
},
),
),
],
);
} } </pre> <h3 id="main-app">Main App</h3> <p>Update your <code>main.dart</code> file:</p> <pre>import 'package:flutter/material.dart'; import 'weather_screen.dart';
void main() { runApp(const MyApp()); }
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return MaterialApp( title: 'Weather App', theme: ThemeData( primarySwatch: Colors.blue, fontFamily: 'Roboto', brightness: Brightness.light, ), darkTheme: ThemeData( primarySwatch: Colors.blue, fontFamily: 'Roboto', brightness: Brightness.dark, ), themeMode: ThemeMode.system, home: const WeatherScreen( apiKey: 'YOUR_API_KEY', // Replace with your OpenWeatherMap API key ), ); } } </pre> <h2 id="setting-up-permissions">Setting Up Permissions</h2> <p>For Android, update the <code>android/app/src/main/AndroidManifest.xml</code> file:</p> <pre><manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Internet permissions --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Location permissions --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
&lt;application
...
&lt;/application&gt;
</manifest> </pre> <p>For iOS, update the <code>ios/Runner/Info.plist</code> file:</p> <pre><key>NSLocationWhenInUseUsageDescription</key> <string>This app needs access to location to show weather information.</string> </pre> <h2 id="testing-the-app">Testing the App</h2> <p>Replace the placeholder API key in <code>main.dart</code> with your own OpenWeatherMap API key. Then run the app:</p> <pre>flutter run </pre> <h2 id="enhancements-to-consider">Enhancements to Consider</h2> <p>Here are some additional features you could add to your weather app:</p> <ol> <li><strong>Theme Toggle</strong>: Add an option to switch between light and dark themes</li> <li><strong>More Weather Details</strong>: Show additional information like pressure, visibility, sunrise/sunset times</li> <li><strong>Weather Alerts</strong>: Display any weather warnings or alerts for the selected location</li> <li><strong>Favorite Locations</strong>: Allow users to save their favorite locations</li> <li><strong>Hourly Forecast</strong>: Display an hourly forecast for the next 24 hours</li> <li><strong>Weather Animations</strong>: Add animations based on the current weather conditions</li> <li><strong>Offline Support</strong>: Cache weather data for offline access</li> </ol> <h2 id="conclusion">Conclusion</h2> <p>Congratulations! You've successfully built a weather app using Flutter and the OpenWeatherMap API. This app demonstrates important concepts like making API calls, processing JSON data, handling user location, and building a responsive UI with Flutter widgets.</p> <p>This project serves as a great foundation for more complex weather applications. Feel free to enhance it with additional features or customize the UI to match your own design preferences.</p> <p>Remember to secure your API keys if you plan to share or publish your code. Happy coding!</p>