Build a Simple Weather App Using Flutter and OpenWeatherMap API

This build a simple weather app using flutter is posted by seven.srikanth at 5/3/2025 3:24:27 PM



<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 &#39;dart:convert&#39;; import &#39;package:http/http.dart&#39; as http;

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

WeatherService();

Future&lt;Map&lt;String, dynamic&gt;&gt; getWeatherByCity(String city) async { final response = await http.get( Uri.parse(&#39;$baseUrl/weather?q=$city&amp;appid=$apiKey&amp;units=metric&#39;), );

if (response.statusCode == 200) {
  return json.decode(response.body);
} else {
  throw Exception(&amp;#39;Failed to load weather data: ${response.statusCode}&amp;#39;);
}

}

Future&lt;Map&lt;String, dynamic&gt;&gt; getWeatherByLocation( double latitude, double longitude) async { final response = await http.get( Uri.parse( &#39;$baseUrl/weather?lat=$latitude&amp;lon=$longitude&amp;appid=$apiKey&amp;units=metric&#39;), );

if (response.statusCode == 200) {
  return json.decode(response.body);
} else {
  throw Exception(&amp;#39;Failed to load weather data: ${response.statusCode}&amp;#39;);
}

}

Future&lt;Map&lt;String, dynamic&gt;&gt; getForecast( double latitude, double longitude) async { final response = await http.get( Uri.parse( &#39;$baseUrl/forecast?lat=$latitude&amp;lon=$longitude&amp;appid=$apiKey&amp;units=metric&#39;), );

if (response.statusCode == 200) {
  return json.decode(response.body);
} else {
  throw Exception(&amp;#39;Failed to load forecast data: ${response.statusCode}&amp;#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&lt;String, dynamic&gt; json) { return Weather( cityName: json[&#39;name&#39;], temperature: json[&#39;main&#39;][&#39;temp&#39;].toDouble(), description: json[&#39;weather&#39;][0][&#39;description&#39;], humidity: json[&#39;main&#39;][&#39;humidity&#39;], windSpeed: json[&#39;wind&#39;][&#39;speed&#39;].toDouble(), icon: json[&#39;weather&#39;][0][&#39;icon&#39;], date: DateTime.fromMillisecondsSinceEpoch(json[&#39;dt&#39;] * 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&lt;String, dynamic&gt; json) { return ForecastDay( date: DateTime.fromMillisecondsSinceEpoch(json[&#39;dt&#39;] * 1000), maxTemp: json[&#39;main&#39;][&#39;temp_max&#39;].toDouble(), minTemp: json[&#39;main&#39;][&#39;temp_min&#39;].toDouble(), icon: json[&#39;weather&#39;][0][&#39;icon&#39;], description: json[&#39;weather&#39;][0][&#39;description&#39;], ); } } </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 &#39;package:flutter/material.dart&#39;; import &#39;package:flutter_spinkit/flutter_spinkit.dart&#39;; import &#39;package:geolocator/geolocator.dart&#39;; import &#39;package:intl/intl.dart&#39;; import &#39;weather_model.dart&#39;; import &#39;weather_service.dart&#39;;

class WeatherScreen extends StatefulWidget { final String apiKey;

const WeatherScreen({Key? key, required this.apiKey}) : super(key: key);

@override _WeatherScreenState createState() =&gt; _WeatherScreenState(); }

class _WeatherScreenState extends State&lt;WeatherScreen&gt; { final TextEditingController _cityController = TextEditingController(); Weather? _currentWeather; List&lt;ForecastDay&gt; _forecast = []; bool _isLoading = false; String _errorMessage = &#39;&#39;;

late WeatherService _weatherService;

@override void initState() { super.initState(); _weatherService = WeatherService(apiKey: widget.apiKey); _getCurrentLocation(); }

Future&lt;void&gt; _getCurrentLocation() async { setState(() );

try {
  bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
  if (!serviceEnabled) {
    setState(() {
      _errorMessage = &amp;#39;Location services are disabled.&amp;#39;;
      _isLoading = false;
    });
    return;
  }

  LocationPermission permission = await Geolocator.checkPermission();
  if (permission == LocationPermission.denied) {
    permission = await Geolocator.requestPermission();
    if (permission == LocationPermission.denied) {
      setState(() {
        _errorMessage = &amp;#39;Location permissions are denied.&amp;#39;;
        _isLoading = false;
      });
      return;
    }
  }

  if (permission == LocationPermission.deniedForever) {
    setState(() {
      _errorMessage = &amp;#39;Location permissions are permanently denied.&amp;#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 = &amp;#39;Error getting location: $e&amp;#39;;
    _isLoading = false;
  });
}

}

Future&lt;void&gt; _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[&amp;#39;coord&amp;#39;][&amp;#39;lat&amp;#39;];
  final lon = data[&amp;#39;coord&amp;#39;][&amp;#39;lon&amp;#39;];
  await _getForecast(lat, lon);
} catch (e) {
  setState(() {
    _errorMessage = &amp;#39;Error fetching weather: $e&amp;#39;;
    _isLoading = false;
  });
}

}

Future&lt;void&gt; _getWeatherByLocation(double lat, double lon) async { try { final data = await _weatherService.getWeatherByLocation(lat, lon); setState(() ); } catch (e) { setState(() { _errorMessage = &#39;Error fetching weather: $e&#39;; _isLoading = false; }); } }

Future&lt;void&gt; _getForecast(double lat, double lon) async { try { final data = await _weatherService.getForecast(lat, lon); List&lt;ForecastDay&gt; forecast = [];

  // Group forecast by day (every 24 hours)
  int i = 0;
  while (i &amp;lt; data[&amp;#39;list&amp;#39;].length) {
    forecast.add(ForecastDay.fromJson(data[&amp;#39;list&amp;#39;][i]));
    i += 8; // Skip to next day (3-hour intervals, 8 times = 24 hours)
  }

  setState(() {
    _forecast = forecast;
  });
} catch (e) {
  print(&amp;#39;Error fetching forecast: $e&amp;#39;);
}

}

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(&#39;Weather App&#39;), 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(&#39;No weather data available&#39;)); }

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: &#39;Search city&#39;, 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(&#39;EEEE, MMM d&#39;).format(weather.date), style: TextStyle(fontSize: 16, color: Colors.grey[700]), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Image.network( &#39;https://openweathermap.org/img/wn/$@2x.png&#39;, width: 80, height: 80, ), Text( &#39;${weather.temperature.toStringAsFixed(1)}&#176;C&#39;, 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, &#39;$%&#39;, &#39;Humidity&#39;), _buildWeatherDetail(Icons.air, &#39;$ m/s&#39;, &#39;Wind Speed&#39;), ], ), ], ), ), ); }

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(
        &amp;#39;5-Day Forecast&amp;#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(&amp;#39;E&amp;#39;).format(day.date),
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 4),
                  Image.network(
                    &amp;#39;https://openweathermap.org/img/wn/${day.icon}.png&amp;#39;,
                    width: 40,
                    height: 40,
                  ),
                  const SizedBox(height: 4),
                  Text(
                    &amp;#39;${day.maxTemp.toStringAsFixed(0)}&amp;#176;&amp;#39;,
                    style: const TextStyle(fontWeight: FontWeight.bold),
                  ),
                  Text(
                    &amp;#39;${day.minTemp.toStringAsFixed(0)}&amp;#176;&amp;#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 &#39;package:flutter/material.dart&#39;; import &#39;weather_screen.dart&#39;;

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

class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key);

@override Widget build(BuildContext context) { return MaterialApp( title: &#39;Weather App&#39;, theme: ThemeData( primarySwatch: Colors.blue, fontFamily: &#39;Roboto&#39;, brightness: Brightness.light, ), darkTheme: ThemeData( primarySwatch: Colors.blue, fontFamily: &#39;Roboto&#39;, brightness: Brightness.dark, ), themeMode: ThemeMode.system, home: const WeatherScreen( apiKey: &#39;YOUR_API_KEY&#39;, // 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>&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt; &lt;!-- Internet permissions --&gt; &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&gt; &lt;!-- Location permissions --&gt; &lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt; &lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_LOCATION&quot; /&gt;

&amp;lt;application
    ...
&amp;lt;/application&amp;gt;

&lt;/manifest&gt; </pre> <p>For iOS, update the <code>ios/Runner/Info.plist</code> file:</p> <pre>&lt;key&gt;NSLocationWhenInUseUsageDescription&lt;/key&gt; &lt;string&gt;This app needs access to location to show weather information.&lt;/string&gt; </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>


Tags: flutter,markdown,generated








0 Comments
Login to comment.
Recent Comments