Build A Weather App In Flutter With OpenWeatherMap API
Build a Weather App in Flutter with OpenWeatherMap API
Hey everyone! đ Ever wanted to build your own weather app? Today, weâre diving into how to integrate the OpenWeatherMap API into a Flutter application. Weâll be walking through everything, from setting up your API key to displaying the weather data on the screen. So, buckle up, and letâs get started! This OpenWeatherMap API tutorial Flutter guide will show you how to fetch weather data, parse the JSON response, and present it beautifully in your Flutter app. Letâs get you from zero to hero in building weather apps, making sure you understand every nook and cranny.
Table of Contents
- Setting up Your OpenWeatherMap Account and API Key
- Project Setup in Flutter
- Making API Requests with Flutter
- Displaying Weather Data in Your Flutter App
- Handling the JSON Response
- Adding Error Handling and Improving the UI
- Enhancing the App with Location Services and Advanced Features
- Conclusion: Your Weather App Journey
Setting up Your OpenWeatherMap Account and API Key
First things first, youâll need an API key from OpenWeatherMap. If you donât already have an account, head over to their website and sign up. Itâs pretty straightforward, and they even have a free tier thatâs perfect for learning and small projects. Once youâre signed up, navigate to your account dashboard and generate an API key. Remember, keep this key safe! Donât share it in public repositories or expose it in your appâs code directly. Weâll explore how to store it securely later.
Now, letâs talk about the structure of the API. OpenWeatherMap provides various API endpoints for different types of weather data. For this tutorial, weâll focus on the âcurrent weather dataâ endpoint, which gives us the current weather conditions for a specific city. The API endpoint will look something like this:
api.openweathermap.org/data/2.5/weather?q={city name}&appid={your api key}
In this URL, replace
{city name}
with the name of the city you want to get the weather for (e.g., London, New York) and
{your api key}
with the API key you generated. The
2.5
represents the API version. Other parameters allow for specifying units of measurement (metric or imperial), language, and more. You can also get weather data by geographical coordinates (latitude and longitude). You can explore this and other endpoints on the OpenWeatherMap website. Understanding these parameters and how to use them is essential for customizing the weather information your app displays. You can retrieve weather information for multiple cities, customize the display of the data, and make your app even more appealing to users. This is a fundamental step to your project, so, pay close attention to it.
Project Setup in Flutter
Alright, letâs get our hands dirty with Flutter! Create a new Flutter project using your favorite IDE or the command line. Open your terminal and run
flutter create weather_app
. Replace
weather_app
with your projectâs name. After the project is created, navigate into the project directory using
cd weather_app
.
Next, we need to add a few dependencies to our
pubspec.yaml
file. The main ones we need are
http
for making API requests and
geolocator
to get the userâs location. Open your
pubspec.yaml
file and add the following under the
dependencies:
section:
http: ^0.13.6
geolocator: ^9.0.2
Make sure to save the file after adding these dependencies. Then, run
flutter pub get
in your terminal to fetch the packages. This command will download and install the packages and their dependencies, making them available in your project. This is a crucial step for setting up the environment. The
http
package allows us to make network requests to fetch the weather data. The
geolocator
package will help us retrieve the userâs current location, enabling us to display weather data relevant to their area.
Making API Requests with Flutter
With our project set up and dependencies in place, we can now write the code to make API requests. Create a new file called
weather_service.dart
(or a name of your choice) in the
lib
directory. This file will contain the logic for fetching weather data from the OpenWeatherMap API. Inside
weather_service.dart
, letâs import the
http
package and define a function that will fetch the weather data.
import 'dart:convert';
import 'package:http/http.dart' as http;
class WeatherService {
final String apiKey;
WeatherService({required this.apiKey});
Future<Map<String, dynamic>?> getWeather(String city) async {
final url = Uri.parse('api.openweathermap.org/data/2.5/weather?q=$city&appid=$apiKey&units=metric');
final response = await http.get(url);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
print('Request failed with status: ${response.statusCode}.');
return null;
}
}
}
In this code, we first import the necessary packages. Then, we define a class
WeatherService
that takes the API key in its constructor. The
getWeather
function takes a city name as input, constructs the API URL, and makes a GET request to the OpenWeatherMap API. We use
jsonDecode
to parse the JSON response. If the request is successful (status code 200), we return the parsed JSON data; otherwise, we print an error message and return null. We have to include the
units=metric
parameter to make sure the temperature is in Celsius.
Displaying Weather Data in Your Flutter App
Now, letâs move on to the UI part of our app. Open your
main.dart
file and replace the boilerplate code with the following:
import 'package:flutter/material.dart';
import 'weather_service.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),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _cityController = TextEditingController();
String? _weatherDescription;
double? _temperature;
final String apiKey = 'YOUR_API_KEY'; // Replace with your actual API key
final WeatherService _weatherService = WeatherService(apiKey: 'YOUR_API_KEY');
Future<void> _getWeather() async {
final String city = _cityController.text;
final weatherData = await _weatherService.getWeather(city);
if (weatherData != null) {
setState(() {
_weatherDescription = weatherData['weather'][0]['description'];
_temperature = weatherData['main']['temp'];
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Weather App'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _cityController,
decoration: const InputDecoration(
labelText: 'Enter City',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _getWeather,
child: const Text('Get Weather'),
),
const SizedBox(height: 20),
if (_weatherDescription != null) ...[
Text(
'Weather: $_weatherDescription',
style: const TextStyle(fontSize: 18),
),
Text(
'Temperature: ${_temperature?.toStringAsFixed(1)}°C',
style: const TextStyle(fontSize: 18),
),
],
],
),
),
);
}
}
In this code, we have a simple UI with a text field for entering the city name, a button to fetch the weather, and a display area to show the weather description and temperature. The
_getWeather
function is triggered when the button is pressed. It retrieves the city name, calls the
getWeather
function from our
WeatherService
, and updates the UI with the fetched weather data using
setState
. Donât forget to replace
'YOUR_API_KEY'
with your actual API key.
Handling the JSON Response
Once you have the JSON response from the API, youâll need to parse it to extract the relevant data, such as the temperature, weather description, and any other information you want to display. The structure of the JSON response from the OpenWeatherMap API looks like this (it might vary slightly based on the parameters you use, but the core structure is the same):
{
"coord": {
"lon": -0.13,
"lat": 51.51
},
"weather": [
{
"id": 300,
"main": "Drizzle",
"description": "light intensity drizzle",
"icon": "09d"
}
],
"main": {
"temp": 280.46,
"feels_like": 279.79,
"temp_min": 279.79,
"temp_max": 281.33,
"pressure": 1022,
"humidity": 81
},
"visibility": 10000,
"wind": {
"speed": 6.69,
"deg": 240
},
"clouds": {
"all": 100
},
"dt": 1693892701,
"sys": {
"type": 2,
"id": 2075535,
"country": "GB",
"sunrise": 1693863484,
"sunset": 1693910626
},
"timezone": 3600,
"id": 2643743,
"name": "London",
"cod": 200
}
The example above shows a typical JSON response. To access the weather description, youâll need to go through the
weather
array, which contains an object with a
description
key. Similarly, the temperature is found under the
main
object, using the
temp
key. In your
getWeather
function, youâll use the
jsonDecode
function to convert the JSON string to a Dart map. Then, you can access the data by using the keys like this:
final weatherData = jsonDecode(response.body);
final description = weatherData['weather'][0]['description'];
final temperature = weatherData['main']['temp'];
This will allow you to access the information to display in your Flutter application. Remember that the structure of the JSON might vary slightly based on the parameters youâre using in your API request, so be sure to check the OpenWeatherMap API documentation for the exact structure and the meaning of the data it returns. If the API returns an error or you canât parse the data, always handle it gracefully. You can display an error message to the user or retry the request. Understanding the JSON structure is crucial to extracting the necessary information to drive the UI components of your app.
Adding Error Handling and Improving the UI
Now, letâs make our app a bit more robust and user-friendly. First, implement error handling to handle cases where the API request fails or returns an error. We can also add a loading indicator while the data is being fetched and provide feedback to the user if the city isnât found.
Modify your
_getWeather
function to include error handling:
Future<void> _getWeather() async {
setState(() {
_weatherDescription = null;
_temperature = null;
});
final String city = _cityController.text;
if (city.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please enter a city')),
);
return;
}
try {
final weatherData = await _weatherService.getWeather(city);
if (weatherData != null) {
setState(() {
_weatherDescription = weatherData['weather'][0]['description'];
_temperature = weatherData['main']['temp'];
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('City not found')),
);
}
} catch (e) {
print('Error fetching weather: $e');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to fetch weather. Check your internet connection or try again.')),
);
}
}
This code checks if the city input is empty. If it is, a snackbar prompts the user to enter a city. The
try...catch
block handles potential errors during the API request. If an error occurs, it prints an error message and shows an appropriate snackbar. The snackbar helps notify the user about issues. To make your app look more appealing, you can customize the UI. Adding icons, choosing specific fonts and colors, and arranging the elements effectively can significantly improve the user experience. You can also display a loading indicator while the API request is being made. You can change colors, add an image, and modify the fonts to make the app more attractive and user-friendly.
Enhancing the App with Location Services and Advanced Features
Letâs make the app even cooler! Letâs incorporate location services to get the userâs current weather. Start by adding
geolocator
to the
pubspec.yaml
file, as we did earlier. Then, in the
main.dart
file, weâll modify the
_MyHomePageState
to get the userâs location.
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'weather_service.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),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String? _weatherDescription;
double? _temperature;
final String apiKey = 'YOUR_API_KEY'; // Replace with your actual API key
final WeatherService _weatherService = WeatherService(apiKey: 'YOUR_API_KEY');
@override
void initState() {
super.initState();
_getWeatherDataByLocation();
}
Future<void> _getWeatherDataByLocation() async {
try {
LocationPermission permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Handle permission denied
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Location permission is denied.')),
);
return;
}
if (permission == LocationPermission.deniedForever) {
// Handle permission permanently denied
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Location permission is permanently denied.')),
);
return;
}
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
final weatherData = await _weatherService.getWeatherByCoordinates(position.latitude, position.longitude);
if (weatherData != null) {
setState(() {
_weatherDescription = weatherData['weather'][0]['description'];
_temperature = weatherData['main']['temp'];
});
}
} catch (e) {
print('Error getting weather by location: $e');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to get weather by location. Check your internet connection or try again.')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Weather App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (_weatherDescription != null) ...[
Text(
'Weather: $_weatherDescription',
style: const TextStyle(fontSize: 18),
),
Text(
'Temperature: ${_temperature?.toStringAsFixed(1)}°C',
style: const TextStyle(fontSize: 18),
),
],
],
),
),
);
}
}
In this code, we first import
geolocator
. Inside the
_MyHomePageState
, we now have
_getWeatherDataByLocation
. First, we request location permissions using
Geolocator.requestPermission()
. If the user denies the permission, we show a snackbar informing them. If permission is granted, we use
Geolocator.getCurrentPosition()
to get the userâs current location. We then call the
getWeatherByCoordinates
function from
WeatherService
to fetch the weather data based on latitude and longitude. Make sure you add
getWeatherByCoordinates
to the
WeatherService
class.
Future<Map<String, dynamic>?> getWeatherByCoordinates(double latitude, double longitude) async {
final url = Uri.parse('api.openweathermap.org/data/2.5/weather?lat=$latitude&lon=$longitude&appid=$apiKey&units=metric');
final response = await http.get(url);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
print('Request failed with status: ${response.statusCode}.');
return null;
}
}
Remember to replace âYOUR_API_KEYâ with your actual API key. You also need to add the
geolocator
and
http
import in the
weather_service.dart
file. We also can improve the app by adding a background image, dynamic icons that correspond to the weather conditions, and more. Consider adding a settings screen that lets the user choose between Celsius and Fahrenheit. All these upgrades will enhance user engagement and provide a more polished experience.
Conclusion: Your Weather App Journey
Congratulations! đ Youâve now built a basic weather app in Flutter using the OpenWeatherMap API. We covered how to get your API key, set up the project, make API requests, parse the JSON data, and display the weather information. We also explored how to add error handling, improve the UI, and incorporate location services. Thereâs a lot more you can do to enhance your app, from adding more weather details like wind speed and humidity to creating a more intuitive and visually appealing user interface. Donât hesitate to explore and experiment with the OpenWeatherMap API and Flutterâs capabilities. Continue practicing and iterating on your project. Thatâs how you learn and grow as a developer. Keep coding, and happy weather app building, everyone! đ