Dive into the world of Flutter, where everything is a widget! This chapter unveils three fundamental widget categories essential for:
Structure and navigation
Displaying information
Positioning widgets
By the end of the chapter, you’ll construct a social food app called Yummy. You’ll use various widgets to create three distinct tabs: Category, Post, and Restaurant.
Ready? Dive in by taking a look at the starter project.
Widget Initialization: Every journey with Flutter commences with a widget. The runApp() function initializes the app by accepting the root widget, in this case, an instance of Yummy.
Every widget must override the build() method.
The Yummy widget starts by composing a MaterialApp widget to give it a Material Design system look and feel. See https://material.io for more details.
Scaffold defines the app’s visual structure, containing an AppBar and a body for starts.
Styling Your App
Flutter, being cross-platform, supports Android’s Material Design and iOS’s Cupertino design systems.
Android uses the Material Design system, which you’d import like this:
import 'package:flutter/material.dart';
iOS uses the Cupertino system. Here’s how you’d import it:
import 'package:flutter/cupertino.dart';
Throughout this book, you’ll learn to use the Material Design system. You’ll find the look and feel quite customizable!
Note: Switching between Material and Cupertino is beyond the scope of this book. For more information about what these packages offer in terms of UI components, check out:
ColorSelection enum enables users to select and customize the app’s appearance with:
Structured color options. The name listed (e.g. Deep Purple) is what will be displayed.
Each has a label and a color object.
Now, you’ll learn to apply the color themes to your app.
Applying the Theme
In main.dart, import your predefined color themes:
import 'constants.dart';
Locate // TODO: Setup default theme and replace it with the following code to establish your default theme mode and primary color, ignore the red squiggles:
This code snippet sets the global theme mode. It defines both light and dark themes utilizing the color you previously specified, ensuring a cohesive and adaptive visual appearance across your app.
Since the theme can change, you need to remove const from the following two locations:
Locate // Manual theme toggle and change light to dark to observe theme variations. Make sure you do a hot restart.
The two themes look like this:
Next, you’ll create a way to enable users to toggle between light and dark modes and select a custom color theme.
Switching Themes
To enable theme switching within your app, you need to manage state by converting the Yummy widget to a StatefulWidget. The good news is that instead of converting manually, you can just use a right-click menu shortcut to do it automatically.
Right-click the class name Yummy. Then click Show Context Actions from the menu that pops up:
Calling these functions will update the theme or color of your app.
Now, you need to create custom buttons to update the theme.
Creating Custom Buttons to Switch Color and Mode
Now it’s time to create two buttons that will allow your users to:
Switch between light and dark mode.
Select the color theme of the entire app.
Creating a Theme Button
You’ll create a button to toggle between light and dark mode. In lib directory, create a new folder called components and create a new file called theme_button.dart in that directory, add the following code to it:
The ThemeButton widget is initialized with a constructor requiring a function changeThemeMode parameter.
changeThemeMode is a callback function passed as a parameter to be called when the user presses the button. This function notifies the parent widget about the brightness change, enabling it to adjust the theme accordingly.
isBright is a Boolean that checks whether the current theme brightness is light.
An IconButton widget that will display light or dark mode icon based on the isBright Boolean.
IconButton, when pressed, toggles the theme brightness by invoking changeThemeMode.
Next, you’ll create a button for users to select their favorite color to apply the entire app theme.
Creating the Color Button
In lib/components directory, create a new file called color_button.dart and add the following code to it:
With a hot restart, you should see the two new buttons on the top right. Try to switch between light and dark mode and change the color theme.
Next, you’ll learn about an important aspect of building an app — understanding which app structure to use.
Understanding App Structure and Navigation
Establishing your app’s structure from the beginning is important for the user experience. Applying the right navigation structure makes it easy for your users to navigate the information in your app.
Yummy uses the Scaffold widget for its starting app structure. Scaffold is one of the most commonly used Material widgets in Flutter. Next, you’ll learn how to implement it in your app.
Using Scaffold
The Scaffold widget implements all your basic visual layout structure needs. It’s composed of the following parts:
AppBar
BottomSheet
BottomNavigationBar
Drawer
FloatingActionButton
SnackBar
Scaffold has a lot of functionality out of the box!
The following diagram represents some of the previously mentioned items as well as showing left and right nav options:
As you build large-scale apps, you’ll start to compose a staircase of widgets. Widgets composed of other widgets can get really long and messy. It’s a good idea to break your widgets into separate files for readability.
To avoid making your code overly complicated, you’ll create the first of these separate files now.
Your next step is to move code out of main.dart into a new StatefulWidget named Home.
In the lib directory, create a new file called home.dart and add the following:
These aren’t used anymore. With that done, you’ll move on to addressing Scaffold’s bottom navigation.
Adding a BottomNavigationBar
Open home.dart, locate // TODO: Track current tab and replace it with the following:
int tab = 0;
tab property will be used to keep track of the current tab the user is on.
Your next step is to define a list of tabs the user can navigate between. Locate // TODO: Define tab bar destinations and replace it with the following code:
With that complete, your app should look like this:
Now that you’ve set up the bottom navigation bar, you need to implement the navigation between pages.
Navigating Between Pages
To navigate between pages, you first need to define the list of pages the user may potentially navigate to. Still in home.dart, locate the comment // TODO: Define pages and replace it with the following:
final pages = [
// TODO: Replace with Category Card
Container(color: Colors.red),
// TODO: Replace with Post Card
Container(color: Colors.green),
// TODO: Replace with Restaurant Landscape Card
Container(color: Colors.blue)
];
This contains a list of containers with different colors. You’ll replace each one with a unique card soon.
Next, locate the comment // TODO: Switch between pages and replace it and all the body: Padding(...) code with the following:
IndexedStack stacks and displays one widget from pages based on the tab index, preserving the state of all widgets in the stack.
After restarting, your app will look different for each tab item, like this:
Now that you’ve set up your tab navigation, it’s time to create beautiful cards!
Creating Custom Cards
In this section, you’ll compose three cards by combining a mixture of display and layout widgets.
Note: To help construct these cards, the models folder already contains models and mock data for each model to use to display in your custom widgets. Have a look at food_category.dart, post.dart, and restaurant.dart to learn more!
Display widgets handle what the user sees onscreen. Examples of display widgets include:
Text
Image
Button
Layout widgets help with the arrangement of widgets. Examples of layout widgets include:
Display the category name with a smaller title style.
Display the number of restaurants in a small body text style
After these updates, the final CategoryCard looks like this:
Great, you finished the first card! It’s time to move on to the next!
Tip: Leverage Material 3’s typography text theme for consistent text styles across your app, avoiding hardcoded font sizes and colors.
Composing Post Card
It’s time to start composing the next card, the post card. Here’s how it will look by the time you’re done:
PostCard is composed of the following widgets:
Card: Provides a material design card that can hold related pieces of information or content.
Padding: Adds a uniform padding of 16.0 pixels around the content inside it to provide some spacing.
Row: Arranges its children widgets in a horizontal line.
CircleAvatar: Displays the profile image in a circular shape.
SizedBox: Provides a horizontal spacing of 16.0 pixels between the avatar and the text.
Expanded: Takes the remaining space in the row to avoid overflow and ensures the child widget utilizes the available horizontal space.
Column: Arranges its children widgets in a vertical line.
Text (Comment): Displays the post comment, limiting it to 2 lines and truncating any overflow.
Text (Timestamp): Displays how long ago the post was made.
This structure ensures a clean, organized layout where the user’s avatar is displayed alongside their post content, with the post comment and timestamp neatly presented below it.
In the lib/components directory, create a new file called post_card.dart. Add the following code:
Expanded widget makes the child occupy all available space.
Column widget vertically stacks children. MainAxisSize.min aligns them to occupy minimum space. CrossAxisAlignment.start horizontally aligns the child widgets to the left side.
Display two Text widgets, the post contents followed by the post’s timestamp.
After a hot restart, your PostCard widget should look like this:
And that’s all you need to do for the post card. Next, you’ll move on to the final one.
Composing Restaurant Landscape Card
RestaurantLandscapeCard is the last card you’ll create for this chapter. This card lets the user explore popular restaurant trends and order food.
The following widgets compose RestaurantLandscapeCard:
Card: A material design card that contains related pieces of information.
Column: Arranges its children widgets in a vertical line.
ClipRRect: Clips its child with a rounded rectangle border.
AspectRatio: Constrains the child’s aspect ratio.
Image: Displays the restaurant’s image, covering the available space.
ListTile: A widget that contains title and subtitle text.
In the lib/components directory, create a new file called restaurant_landscape_card.dart. Add the following code:
Here, you set up the basic structure for the RestaurantLandscapeCard widget. It simply takes in an instance of Restaurant, which you’ll use later to display data in your UI.
Next, open home.dart and add the following import:
title shows the restaurant’s name with a specific style.
subtitle displays the restaurant’s attributes, truncated if more than one line.
onTap prints the restaurant’s name to the console when tapped.
Save your changes and hot restart. Now, your card looks like this:
Now that Yummy is a StatefulWidget you can add back the const declarations. Open main.dart and change:
runApp(Yummy());
to
runApp(const Yummy());
Then change:
Yummy({super.key});
to
const Yummy({super.key});
When you finish your app, there is one last step - remove the Debug label.
Still in main.dart find // Uncomment to remove Debug banner and remove the // at the beginning of the line. Hot restart your app, and the banner is gone.
You did it! You’ve finished this chapter. Along the way, you’ve applied three different categories of widgets. You learned how to use structural widgets to organize different screens, and you created three custom cards and applied different widget layouts to each.
Well done!
Key Points
Three main categories of widgets are: structure and navigation, displaying information, and positioning widgets.
There are two main visual design systems available in Flutter, Material and Cupertino. They help you build apps that look native on Android and iOS, respectively.
Using the Material theme, you can build quite varied user interface elements to give your app a custom look and feel.
It’s generally a good idea to establish a common theme object for your app, giving you a single source of truth for your app’s style.
The Scaffold widget implements all your basic visual layout structure needs.
The Container widget can be used to group other widgets together.
The Stack widget layers child widgets on top of each other.
Where to Go From Here?
There’s a wealth of Material Design widgets to play with, not to mention other types of widgets — too many to cover in a single chapter.
Fortunately, the Flutter team created a Widget UI component library that shows how each widget works! Check it out here: https://gallery.flutter.dev/
In this chapter, you got started right off with using widgets to build a nice user interface. In the next chapter, you’ll dive into the theory of widgets to help you better understand how to use them.