Flutter Weather App Using Provider

Build a functional weather app using Flutter & Provider

Kenneth Carey
Flutter Community
8 min readSep 14, 2020

--

Introduction

The intent of this article is to demonstrate the use of Provider in a non-trivial application — a weather app.

The app consists of a single screen, on which the user can enter the name of a city. The weather for the current day is displayed and a 3-day daily forecast. The app appearance also adapts for day-time and night-time weather conditions.

Assumptions

I’m going to assume you are at least familiar with Flutter, Provider and Flutter widgets such as Scaffold , Row, Column, SizedBox and Container. If not, check out the link below for an intro to Provider.

Getting Started

We’ll be using VS Code as our IDE and targeting Android devices.

▹ Create a new Flutter app in VS Code or using the Flutter CLI:

flutter create weather_app

▹ Next, we need to add provider, http and intl dependencies to your project. To do this, update your pubspec.yaml file to match the image below.

▹ Install the packages using the Flutter CLI pub get command:

flutter pub get

▹ Navigate to the project lib directory and create the directory structure shown below.

▹ Next, create an asset section in your pubspec.yaml file and add the path to your new images folder.

▹ Copy your weather condition images to your new images folder.

Architecture

For a detailed breakdown of the Weather App architecture, let’s refer to the following diagram:

In this figure, the top, blue boxes are our Provider layer. The Providers are top-most so we can supply data to all widgets below.

Our app has a single home screen, HomeView, which is sub-divided into multiple custom views. A View is a widget made up of other widgets. To manage these views, supply them data and call functions, we have ViewModels.

A view will be rebuilt from the ViewModel when the state has changed and a view will only interact with it’s ViewModel. ViewModels can interact with a service, a view should not. There is also no state management in the views. Instead, the state is kept in the ViewModel itself.

The Service layer contains a single class which hides the REST API implementation details. This layer returns plain old dart objects that model the forecast.

The following sections will detail a weather app implementation based on the above architecture.

openweathermap.org REST API

The application uses the openweathermap.org REST APIs to retrieve the weather forecast.

To get started with openweathermap.org you’ll need to create a free account and obtain an app_id. This app_id must be passed with each API request.

The ‘One Call API’ returns the current and forecast (optional hourly, minute and daily) weather data for supplied coordinates. To get the coordinates of the city entered by the user, we’ll use the ‘Current Weather API’. That returns the current weather and the city coordinates.

To verify your app_id you can hit the REST API via your browser. Substitute your app_id in the URL below and you should see JSON, representing the current weather and city coordinates.

https://samples.openweathermap.org/data/2.5/weather?q=London&appid=439d4b804bc8187953eb36d2a8c26a02

To test the ‘One Call API’ endpoint enter the URL below, substituting your app_id.

https://api.openweathermap.org/data/2.5/onecall?lat=51.9&lon=-8.47&exclude=hourly,minutely&appid=439d4b804bc8187953eb36d2a8c26a02

Models

We need to consume the JSON data returned by the two endpoints above.

weather.dart
Create a file called weather.dart in \lib\models and paste in the code below.

▹ The WeatherConditions enum defines the possible weather conditions returned by the REST API. The weather condition returned in the JSON data is mapped to a corresponding enum value.

▹ The fromDailyJson method is a convenience method for creating a new Weather instance from the response body.

▹ The class does not extend or mix ChangeNotifier. Our ViewModels will be responsible for calling notifyListeners.

forecast.dart
Next, create a new class called forecast.dart in \lib\models.

▹ This class will model the current weather and 3-day forecast.

▹ During the decoding of the JSON data, we are comparing the current time to the sunrise and sunset times to determine if it is day or night at the location.

location.dart
Our final model class is location.dart. Here we’re modelling a city location and the fromJson method takes care of creating a Location instance from the JSON response data.

Weather API

We need to develop a class to make HTTP requests to the openweathermap.org API endpoints and return instances of our model classes above.

As mentioned earlier, we need to obtain the coordinates for a city first and then pass those to another API to get the current weather and 3-day forecast. A httpClient instance handles the network requests and we parse the response JSON into the respective models using the jsonDecode function.

Service

We could consume our weather API class directly in our ViewModel class and eliminate an architecture layer but best practice is to decouple implementation details from client code.

The OpenWeatherMapWeatherApi class extends an abstract class WeatherApi. This abstract class will provide a public-facing interface that our service layer code will interact with. This hides specific weather api implementation details and leaves our code only knowing about the functions and the models returned by the abstract class method definitions.

The service layer consists of a single class acting as a wrapper around an instance which ISA WeatherApi. It exposes a single method which accepts a city and returns an instance of our model Forecast.

Presentation

It’s always good practice to break your screen layout into several custom widgets. For one, it reduces the complexity of your screen and dramatically reduces the number of nested widgets in an individual file. Another benefit is the separation of concerns — a change to one widget does not affect others.

App
To make our weather data models available to all of our widgets we wrap the top of our widget tree in a MultiProvider widget. Each child ChangeNotifierProvider is responsible for creating a single instance of a ViewModel class.

Our App is a StatelessWidget which builds our MaterialApp and HomeView widget.

HomeView
Our most complex widget, the HomeView widget builds our screen UI, supports pull-to-refresh, creates a background gradient and shows a busy indicator.

Some points to note:

▹ Line 35: Consumes ForecastViewModel to be notified when a new Forecast is fetched.

▹ Line 47: RefreshIndicator widget wraps our main ListView to allow the user to pull-to-refresh. The ViewModel makes the weather request in the onRequest callback.

▹ Line 54: Checking if a request is in progress, if so, then a CircularProgressIndicator widget is displayed.

▹ Line 103: Helper method to build our 3-day forecast summary widget — a Row whose child widgets are instances of DailySummaryView.

▹ Line 120: Returns a GradientContainer widget with background colour appropriate to the current day’s weather condition. If it’s night time we default to a night time appropriate gradient.

ForecastViewModel
The ForecastViewModel requests forecast updates on behalf of our views using an instance of the ForecastService class.

Some general points:

▹ Line 15: Mixes ChangeNotifier to allow it to notify consumer Views of any state changes

▹ Line 16: Declares fields for tracking requests in progress and requests completed. As discussed previously, this facilitates Views displaying prompts and busy indicators.

▹ Line 80: Copies Forecast model data to corresponding ViewModel fields and performs some formatting and Kelvin to Celsius temperature conversions.

WeatherSummaryView
Our WeatherSummaryView is a simple StatelessWidget that displays the current temperature (actual & feels-like) and picks an appropriate asset image for the current weather condition.

The asset image is adjusting for night time depending on the weather condition. For example, on a clear day, the sun image is displayed, while on a clear night a crescent moon is shown.

WeatherDescriptionView
Another simple StatelessWidget to display the current weather description.

LastUpdatedView
A StatelessWidget to display the last time the weather was retrieved.

LocationView
Displays the current city and its latitude and longitude coordinates.

DailySummaryView
This StatelessWidget takes an instance of the Weather model in its constructor and builds a widget showing the day of the week, the temperature for that day and an appropriate small weather image corresponding to the weather condition.

You may recall, the HomeView widget creates instances of the DailySummaryView widget for each of the 3 days in the daily forecast list.

CityEntryView
The CityEntryView is a Statefulwidget to allow the user to enter a city and invoke a weather request. This is the only Statefulwidget within the app and is required because it is maintaining a TextEditingController.

The user makes a weather request by clicking the search icon button or submitting after typing. A third approach is also supported whereby the user enters a city without submitting and then performs a pull-to-refresh. This is the reason the CityEntryViewModel city field is kept in sync with every TextField change using the controller addListener method. The ForecastViewModel can then use the CityEntryViewModel city field during a pull-to-refresh weather request.

Notice also how the TextField widget decoration is set to collapse. This is required since our Container is mimicking the appearance of a traditional Text box.

CityEntryViewModel
The CityEntryViewModel invokes a weather request by retrieving an instance of the ForecastViewModel. As discussed above, it also maintains a field to keep the city in sync during pull-to-refresh requests.

GradientContainer
Our last item to discuss is the GradientContainer StatelessWidget. The class is based on one developed by Felix Angelov for his excellent article on building a weather app using the Bloc design pattern.

In this version, I have made some minor modifications by reducing the number of stops and changing the gradient direction to top-left -> bottom right. You can see the subtle gradient effect below.

With & Without Gradient

Summary

We have succeeded in developing a functional weather app using Flutter and Provider package.

During the process, we introduced a multi-layer application architecture, promoting the decoupling of implementation details from client code and the separation of business logic from widgets via the View/ViewModels. This approach to app development should serve you well in future projects.

Stay tuned for updates — I’m currently working on adding support for geolocation packages to remove the need to enter a city!

https://www.twitter.com/FlutterComm

--

--