Hey everyone, today we’re going to build a minimal (but functional) weather app in Flutter which demonstrates how to manage multiple blocs to implement dynamic theming, pull-to-refresh, and much more.
Our weather app will pull real data from an API and demonstrate how to apply a layered architecture to separate presentational logic from business logic.
The finished product will look like this:
Let’s get started!
We’ll start off by creating a brand new Flutter project
flutter create flutter_weather
We can then go ahead and replace the contents of
Note: We are going to use custom assets in our project so we include the entire assets directory.
Now we can install all of our dependencies with
flutter packages get.
For this application we’ll be hitting the metaweather API.
We’ll be focusing on two endpoints:
/api/location/search/?query=$cityto get a locationId for a given city name
/api/location/$locationIdto get the weather for a given locationId
Open https://www.metaweather.com/api/location/search/?query=london in your browser and you’ll see the following response:
We can then get the where-on-earth-id (woeid) and use it to hit the location API.
Navigate to https://www.metaweather.com/api/location/44418 in your browser and you’ll see the response for weather in London. It should look something like this:
Great, now that we know what our data is going to look like, let’s create the necessary data models.
Even though the weather API returns weather for multiple days, for simplicity, we’re only going to worry about today’s weather.
We are going to extract a subset of the data from the API and create a
Weather model which will look something like:
Equatable so that we can compare
Weather instances. By default, the equality operator returns true if and only if this and other are the same instance.
There’s not much happening here; we are just defining our
Weather data model and implementing a
fromJson method so that we can create a
Weather instance from the API response body.
Next, we need to build our
WeatherApiClient which will be responsible for making http requests to the weather API.
WeatherApiClient is the lowest layer in our application architecture (the data provider). Its only responsibility is to fetch data directly from our API.
As we mentioned earlier, we are going to be hitting two endpoints so our
WeatherApiClient needs to expose two public methods:
It should look something like this:
WeatherApiClient has an
httpClient injected via the constructor and it handles making the network request and serializing the response json into the respective data model.
We’ve got our
DataProvider done so it's time to move up to the next layer of our app's architecture: the repository layer.
WeatherRepository serves as an abstraction between the client code and the data provider so that as a developer working on features, you don't have to know where the data is coming from.
WeatherRepository will have a dependency on our
WeatherApiClient that we just created and it will expose a single public method called, you guessed it,
No one needs to know that under the hood we need to make two API calls (one for locationId and one for weather) because no one really cares. All we care about is getting the
Weather for a given city.
WeatherRepository is quite simple and should look something like this:
Awesome! We are now ready to move up to the business logic layer and start building our
Business Logic (Bloc)
WeatherBloc is responsible for receiving
WeatherEvents and converting them into
WeatherStates. It will have a dependency on
WeatherRepository so that it can retrieve the
Weather when a user inputs a city of their choice.
Before jumping into the Bloc we need to define what events our
WeatherBloc will be handling as well as how we are going to represent our
For simplicity, we’re going to start off by having a single event called
We can define it like:
Whenever a user inputs a city, we will
FetchWeather event with the given city and our bloc will responsible for figuring out what the weather is there and returning a new
For the current application, we will have 4 possible states:
WeatherEmpty- our initial state which will have no weather data because the user has not yet selected a city
WeatherLoading- a state which will occur while we are fetching the weather for a given city
WeatherLoaded- a state which will occur if we were able to successfully fetch weather for a given city.
WeatherError- a state which will occur if we were unable to fetch weather for a given city.
We can represent these states like so:
Now that we have our
Events and our
States defined and implemented we are ready to make our
WeatherBloc is very straightforward. To recap, it converts
WeatherStates and has a dependency on the
Tip: Check out the Bloc VSCode Extension in order to take advantage of the bloc snippets and even further improve your efficiency and development speed.
We set our
WeatherEmpty since initially, the user has not selected a city. Then, all that's left is to implement
Since we are only handling the
FetchWeather event all we need to do is
WeatherLoading state when we get a
FetchWeather event and then try to get the weather from the
If we are able to successfully retrieve the weather we then
WeatherLoaded state and if we are unable to retrieve the weather, we
That’s all there is to it! Now we’re ready to move on to the final layer: the presentation layer.
As you’ve probably already seen in other tutorials, we’re going to create a
SimpleBlocDelegate so that we can see all state transitions in our application.
Next, we’re going to set our delegate in our
main function like so:
Lastly, we need to create our
WeatherRepository and inject it into our
App widget is going to start off as a
StatelessWidget which has the
WeatherRepository injected and builds the
MaterialApp with our
Our Weather Widget will be a
StatefulWidget responsible for creating and disposing a
All that’s happening in this widget is we’re using
BlocBuilder with our
WeatherBloc in order to rebuild our UI based on state changes in our
You’ll notice that we are referencing a
CombinedWeatherTemperature widget which we will create in the following sections.
Location widget is simple; it displays the current location.
LastUpdated widget is also super simple; it displays the last updated time so that users know how fresh the weather data is.
Note: We are using
TimeOfDay to format the
DateTime into a more human-readable format.
Combined Weather Temperature
CombinedWeatherTemperature widget is a compositional widget which displays the current weather along with the temperature. We are still going to modularize the
WeatherConditions widgets so that they can all be reused.
Note: We are using two unimplemented widgets:
Temperature which we will create next.
WeatherConditions widget will be responsible for displaying the current weather conditions (clear, showers, thunderstorms, etc...) with an icon.
Tip: Check out icons8 for the assets used in this tutorial.
Temperature widget will be responsible for displaying the average, min, and max temperatures.
The last thing we need to implement to have a functional app is our
CitySelection widget which allows users to type in the name of a city.
CitySelection widget will allow users to input a city name and pass the selected city back to the
CitySelection needs to be a
StatefulWidget because it has to maintain a
Note: When we press the search button we use
Navigator.pop and pass the current text from our
TextController back to the previous view.
At this point we have a fully functioning weather app but upon running it you’ll notice it has a few problems:
- We have no way to refresh the weather data after it is fetched
- The UI is very plain
- Everything is in Celsius and we have no way to change the units
Let’s address these problems and take our Weather App to the next level!
In order to support pull-to-refresh we will need to update our
WeatherBloc to handle a second event:
Next, we need to update our
mapEventToState to handle a
Lastly, we need to update our presentation layer to use a
In order to use the
RefreshIndicator we had to create a
Completer which allows us to produce a
Future which we can complete at a later time.
That’s it! We now have solved problem #1 and users can refresh the weather by pulling down.
Next, let’s tackle the plain looking UI by creating a
ThemeBloc is going to be responsible for converting
ThemeEvents are going to consist of a single event called
WeatherChanged which will be dispatched whenever the weather conditions we are displaying have changed.
ThemeState will consist of a
ThemeData and a
MaterialColor which we will use to enhance our UI.
Now, we can implement our
ThemeBloc which should look like:
Even though it’s a lot of code, the only thing in here is logic to convert a
WeatherCondition to a new
We can now update our
App widget to create a
ThemeBloc and use
BlocBuilder to react to changes in
App widget will now be responsible for creating and disposing of a
ThemeBloc we need to refactor it into a
Note: We are using
BlocProvider to make our
ThemeBloc globally available using
The last thing we need to do is create a cool
GradientContainer widget which will color our background with respect to the current weather conditions.
Now we can use our
GradientContainer in our
Weather widget like so:
We are accessing our
BlocProvider.of<ThemeBloc>(context) and are then dispatching a
WeatherChanged event on each
We also wrapped our
GradientContainer widget with a
ThemeBloc so that we can rebuild the
GradientContainer and it's children in response to
Awesome! We now have an app that looks way nicer (in my opinion 😛) and have tackled problem #2.
All that’s left is to handle unit conversion between Celsius and Fahrenheit. To do that we’ll create a
Settings widget and a
We’ll start off by creating our
SettingsBloc which will convert
SettingsEvents will consist of a single event:
SettingsState will simply consist of the current
Lastly, we need to create our
All we’re doing is using Fahrenheit if
TemperatureUnitsToggled is dispatched and the current units are Celsius and vice versa.
Now we need to add our
SettingsBloc to our
Again, we’re making
SettingsBloc globally accessible using
BlocProvider and we are also disposing it in the
Now we need to create our
Settings widget from which users can toggle the units.
BlocProvider to access the
SettingsBloc via the
BuildContext and then using
BlocBuilder to rebuild our UI based on
Our UI consists of a
ListView with a single
ListTile which contains a
Switch that users can toggle to select Celsius vs. Fahrenheit.
Note: In the switch’s
onChanged method we dispatch a
TemperatureUnitsToggled event to notify the
SettingsBloc that the temperature units have changed.
Next, we need to allow users to get to the
Settings widget from our
We can do that by adding a new
IconButton in our
We’re almost done! We just need to update our
Temperature widget to respond to the current units.
And lastly, we need to inject the
TemperatureUnits into the
🎉 That’s all there is to it! We’ve now successfully implemented a weather app in flutter using the bloc and flutter_bloc packages and we’ve successfully separated our presentation layer from our business logic. 🎉
The full source for this example can be found here.
If you enjoyed this exercise as much as I did you can support me by ⭐️the repository, or 👏 for this story.