The full source code for the following post is available here in the GitHub Repo
Video
In my last blog post, I demonstrated how to build a basic app with Flutter. This app would query weather data from the OpenWeather API and display it on screen. This next blog post takes that app as a starting point and builds a beautiful user interface into it. Let’s get started!
Getting Started
First things first. Looking at the above image of what we will be creating, you will notice that we now pull additional data from OpenWeather. Along with the current weather, we also grab the hourly forecast as well as the daily forecast.
Let’s work on adding that functionality to our app.
First, we will need to create the following models.
Daily
Hourly
Forecast
This is what they should all contain.
daily.dart
hourly.dart
forecast.dart
We are also going to update our weather.dart model to the following
weather.dart
Adding an extension
We will need a couple of small extensions to the String class to help format text. To do this we will create a new file called extensions.dart in the root of the project. Here is the code to insert into that file.
extensions.dart
This will allow us to take text like this: “partly cloudy” and turn it into this: “Partly Cloudy”. Much nicer!
Adding another dependency
Later on, we will need to convert a timestamp to a different date format. To do this we will need to add the intl: ^0.16.1 dependency to the pubspec.yaml file. For detailed instructions on how to do this check out the previous blog post.
Updating main.dart
We have a couple of small updates to main.dart. This update allows for you to input your own location to be used to fetch weather data as well as a set up for you to eventually add on multiple locations. This blog post doesn’t go that far but we are going to lay the groundwork for that to happen.
main.dart
This is what your MyApp class should look like. We have added the locations variable, populated it with a location and passed it to our CurrentWeatherPage widget.
Setting up the main view
We will be replacing the CurrentWeatherPage() class and the _CurrentWeatherPageState() with the following code.
currentWeather.dart
We will be going over all the details of this code snippet later on throughout the rest of the blog post. For now, just make note of the three methods being called inside of the ListView.
Fetching the Weather Data
Inside currentWeather.dart there is the method getCurrentWeather() we are going to insert the following method directly below.
currentWeather.dart
Don’t forget the insert your API Key!
This method should look very familiar, it is almost exactly the same as the one we built in the last blog post so I’m not going to spend any more time going over it as the details can all be found there.
Using Future Builder to Asynchronously load data
We will need to add three more methods, each uses the FutureBuilder that we referenced in the first blog post. The first method is to load the current weather data, the second is to load the hourly forecast and the third is for the daily forecast.
currentWeather.dart
These methods should all seem very familiar if you have read through the first blog post. Therefore I won’t spend too much time on them.
Build the current weather card
The first card of information we are going to build is the big blue one at the very top. Looking at it we have 6 data points.
Current temperature
High temperature
Low temperature
Feels like temperature
Description
Icon
From this, you can see that we have 5 Text widgets and 1 Image widget. Looking at the background there is one solid blue color with a wavey line over top. This waving line was drawn with a widget called ClipPath. We will look into that a little later.
To try to understand how to build and position this widget a little bit better I came up with this diagram.
Red box = container
Add padding and margin to the widgets within
Green box = column
Stacks widgets vertically
Purple box = row
Stacks widgets horizontally
With this in mind, it should make a little bit more sense how to create this widget and position each of those widgets in the correct spot.
Create the method
Let’s create a method to build this card. The method definition should look like this: Widget weatherBox(Weather _weather). It will be called weatherBox, it will return a widget and take a Weather instance as input.
Now, lets add that first outer container.
currentWeather.dart
I’ve added some padding, margin, height and decoration properties to the container which should be pretty self-explanatory.
Now, let’s add a Row as the child of this container.
currentWeather.dart
Starting with the left side of the Row let’s add that Column. We are also going to wrap this Column in the Expanded widget to have it expand and take up as much space as possible within its parent.
currentWeather.dart
The crossAxisAlignment and mainAxisSize properties will ensure the content is left aligned and will minimize the amount of free space along the main axis.
The first thing inside this column is the weather icon, we will come back to this later. The next widget is the description text and the High and Low text. Let’s add those widgets!
currentWeather.dart
We will almost always wrap our text widgets inside containers to help position them. Here we add some margin to the containers. We also add some text styles to each text widget.
Moving on to the right side of the Row we will have another Column.
currentWeather.dart
You will notice that we once again have 2 containers with a text widget each. Now, let’s look into getting this icon!
Weather icons
There are 16 different weather icon names that OpenWeather can specify. Inside the GitHub repository, I have uploaded all 16 icons we will be using. You can add them into your project inside a folder with the following path: assets/icons/.
Add the info to pubspec.yaml
Ensure that you add the following line inside pubspec.yaml under the flutter: line.
pubspec.yaml
Ensure that you pay attention to the white space! YAML is sensitive to white space.
Methods to get icons
Back in currentWeather.dart lets add the following 2 methods.
currentWeather.dart
These methods will return an Image widget with the specified icon.
Going back into the weatherBox method we can add the first method inside our column.
currentWeather.dart
ClipPath
We are almost done with this first box in our weather app. Now for the hardest part, ClipPath! ClipPath allows you to draw shapes and lines. We can specify these shapes and lines with different methods such as:
For Shapes
addRect()
Adds a rectangle
addOval()
Adds an oval
addArc()
Adds an arc
There are many more methods to draw shapes but these are some common ones
To draw lines
quadraticBezierTo
Will draw a line based a 3 given points. This is what we will be using.
cubicTo
Similar to quadraticBezierTo except it uses 4 points
There are also several more methods to draw shapes but these are again some of the common ones
quadraticBezierTo
Let’s look deeper into quadraticBezierTo.
Source: Wikipedia
Here is a visual demonstration of quadraticBezierTo in action. You can see that there is a line between P0 and P1. These points move at the same rate to P1 and P2 along with a point that progresses along that line at the same rate which will then draw a curve. Take a little while to familiarize yourself with the above visual before moving on to some code.
Coding this clip path
We can add the following class definition to the bottom of our currentWeather.dart file
currentWeather.dart
Looking at this code we call the quadraticBezierTo three times to draw three curves, two convex and one concave.
The Path() instance will draw a line based on a given point that you can move around. We set the point to start at (0, size.height - 20). This is the far left side and 20 pixels from the bottom.
Then we draw our three curves moving towards the right side of the screen. Then we draw down to the bottom with path.lineTo(size.width, size.height); and then back to the right with path.lineTo(0, size.height);.
In the end, it should look something like this
Add the clip path to the weatherBox
There are a few more things we need to change in the weatherBox to make it work. First of all, we will need to use the Stack widget. This widget allows us to stack widgets on a Z index. So we can place our text widgets on top of our ClipPath.
Secondly, we need to give our ClipPath something to clip. i.e. a Container widget.
Thirdly we need to put it all together to create on HUGE widget tree! Ideally, you would want to break this up into a bunch of different smaller widgets or methods but this blog post is about widget design and not proper code structure. Anyways, here is the full method
currentWeather.dart
Running The App
After you run your app you should see something like this! Looks pretty sweet!
Building the Second Weather Card
Next up is building this card.
Looking at it you can see that it contains additional data from the current weather. There are 6 Text widgets for this data. Here is the widget breakdown again.
Red box = container
Add padding and margin to the widgets within
Green box = column
Stacks widgets vertically
Purple box = row
Stacks widgets horizontally
Coding the Second Weather Card
Let’s add the following method definition Widget weatherDetailsBox(Weather _weather).
Now, let’s add that first outer Container.
currentWeather.dart
There are some properties I have added for positioning as well as rounding the corners and adding a box-shadow.
Now, let’s add a Row as the child of this container.
currentWeather.dart
Finally, let’s add the first Column which stores the Text widgets.
currentWeather.dart
The other two Columns have the exact same structure so feel free to add those now.
Final outcome of the weatherDetailsBox() method
currentWeather.dart
This is what your weatherDetailsBox() method should now look like. Running the code you should see the following.
Time Stamp Conversion
Before we move onto the forecast widgets we will need to add some methods to convert timestamps into different date formats. Here are those methods.
currentWeather.dart
Hourly Forecast
Now, let’s build the hourly forecast boxes.
You can see that there is a list of widgets. To build this we are going to be using a ListView widget.
Red box = Container
Add padding and margin to the widgets within
Green box = Column
Stacks widgets vertically
Blue box = ListView
Creates a scrollable list of widgets
Coding the Hourly Forecast List
Let’s add the following method definition Widget hourlyBoxes(Forecast _forecast).
Now, let’s add that first outer Container.
currentWeather.dart
List Builder
There is a widget in Flutter called the List builder. It will allow you to pass in an array of elements and dynamically build a list of widgets based on that array. For example, we will be passing in an array of Hourly objects and for each object, we will build a Container widget.
currentWeather.dart
Here you can see the ListView.builder() in action! All we need to do is fill in the Container it returns for each element.
currentWeather.dart
There we go! We should not have a list of the hourly weather! Here is the method in full.
currentWeather.dart
This is what you should see now when you run the app.
Daily Forecast
Let’s move on to the final group of widgets!
Once again let’s break it down.
Red box = Container
Add padding and margin to the widgets within
Blue box = ListView
Creates a scrollable list of widgets
Purple box = Row
Stacks widgets horizontally
Coding the Daily Forecast List
Let’s add the following method definition Widget dailyBoxes(Forecast _forecast).
Now lets add another ListBuilder() wrapped in an Expanded()
currentWeather.dart
Because we are inserting this ListView inside of another ListView we will need to add a few special properties in order for it to work as expected. These are shrinkWrap: true and physics: ClampingScrollPhysics().
currentWeather.dart
Go ahead and add these last additions to the Container widget. This is what the method should look like when it’s complete.
currentWeather.dart
This is what you should see now when you run the app.
App Bar
Last but not least we need to create a title at the top of the screen to display the current location. For a future addition to the app, we could make this title a button that could display a dropdown to select a different location.
Let’s take a look at how this was built.
Red box = Container
Add padding and margin to the widgets within
Purple box = Row
Stacks widgets horizontally
Coding the app bar
Add the following method definition Widget createAppBar(List<Location> locations, Location location, BuildContext context).
Let’s insert that Container into the method
currentWeather.dart
On this Container, we have some padding, margin, and BoxDecoration.
Let’s add the Row inside the Container
currentWeather.dart
Inside this Row, we use the TextSpan widget to display text with multiple styles. We also use the Icon widget to display a downward arrow icon.
Here is the method in full.
currentWeather.dart
Finishing Touches
Congratulations! You have now built an amazing-looking app with Flutter! If you are feeling adventurous try to add in the ability to change your location from a list you define in main.dart.
Feel free the check out the full project over on GitHub.