Flutter Weather App Using Provider
Build a functional weather app using Flutter & Provider
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.
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!