Flutter is Google's UI toolkit for building beautiful, natively compiled apps for mobile, web, and desktop from a single codebase. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.
In this codelab, you'll extend a basic, mobile Flutter app to include interactivity. You'll also create a second page (called a route) that the user can navigate to. Finally, you'll modify the app's theme (color). This codelab extends part 1, where you create an infinite lazily loaded list, but we'll provide the starting code, if you'd like to start with part 2.
What you'll learn in part 2
You'll start with a simple mobile app that generates an endless list of proposed names for a startup company. By the end of the codelab, your end users can select and unselect names, saving the best ones. Tapping the list icon in the upper right of the app bar navigates to a new page (called a route) that lists only the favorited names.
The following animated GIF shows how the finished app will work.
If you have worked through part 1 of this codelab, you already have the starting app, startup_namer. You can proceed to the next step.
If you don't have startup_namer, no fear, you can get it using the following instructions.
Create a simple templated Flutter app using the instructions in Create the app. Name the project
startup_namer
(instead of flutter_app
).
Delete all of the code from
lib/main.dart
. Replace it with the code from this file, which displays an infinite, lazily loaded list of proposed startup names.
Update
pubspec.yaml
by adding the English words package:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
english_words: ^3.1.5 // NEW
The English words package generates pairs of random words, which are used as potential startup names.
While viewing the pubspec in Android Studio's editor view, click Pub get in the upper right, which pulls the package into your project. You should see the following in the console:
flutter pub get
Running "flutter pub get" in startup_namer...
Process finished with exit code 0
Run the app.
Scroll as far as you want, viewing a continual supply of proposed startup names.
In this step, you'll add heart icons to each row. In the next step, you'll make them tappable and save the favorites.
Add a
_saved
Set
to _RandomWordsState
. This Set
stores the word pairings that the user favorited. Set
is preferred to List
because a properly implemented Set
doesn't allow duplicate entries.
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _saved = Set<WordPair>(); // NEW
final _biggerFont = TextStyle(fontSize: 18.0);
...
}
In the
_buildRow
function, add an alreadySaved
check to ensure that a word pairing has not already been added to favorites.
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair); // NEW
...
}
In _buildRow()
, you'll also add heart-shaped icons to the ListTile
objects to enable favoriting. In the next step, you'll add the ability to interact with the heart icons.
Add the icons after the text, as shown below:
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Icon( // NEW from here...
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
), // ... to here.
);
}
Hot reload the app.
You should now see open hearts on each row, but they are not yet interactive.
Android | iOS |
Problems?
If your app isn't running correctly, you can use the code at the following link to get back on track.
In this step, you'll make the heart icons tappable. When the user taps an entry in the list, toggling its favorited state, that word pairing is added or removed from a set of saved favorites.
To do that, you'll modify the _buildRow
function. If a word entry has already been added to favorites, tapping it again removes it from favorites. When a tile has been tapped, the function calls setState()
to notify the framework that state has changed.
Add
onTap
to the _buildRow
method, as shown below:
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
onTap: () { // NEW lines from here...
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
}, // ... to here.
);
}
Hot reload the app.
You should be able to tap any tile to favorite or unfavorite the entry. Tapping a tile generates an implicit ink splash animation emanating from the tap point.
Android | iOS |
Problems?
If your app isn't running correctly, you can use the code at the following link to get back on track.
In this step, you'll add a new page (called a route in Flutter) that displays the favorites. You'll learn how to navigate between the home route and the new route.
In Flutter, the Navigator
manages a stack containing the app's routes. Pushing a route onto the Navigator
's stack updates the display to that route. Popping a route from the Navigator
's stack returns the display to the previous route.
Next, you'll add a list icon to the AppBar
in the build
method for _RandomWordsState
. When the user clicks the list icon, a new route that contains the saved favorites is pushed to the Navigator
, displaying the icon.
Add the icon and its corresponding action to the
build
method:
class _RandomWordsState extends State<RandomWords> {
...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Startup Name Generator'),
actions: [
IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
],
),
body: _buildSuggestions(),
);
}
...
}
Add a
_pushSaved()
function to the _RandomWordsState
class.
void _pushSaved() {
}
Hot reload the app. The list icon
appears in the app bar. Tapping it does nothing yet because the
_pushSaved
function is empty.
Next, you'll build a route and push it to the Navigator
's stack. That action changes the screen to display the new route. The content for the new page is built in MaterialPageRoute
's builder
property in an anonymous function.
Call
Navigator.push
, as shown below, which pushes the route to the Navigator's stack. The IDE will complain about invalid code, but you will fix that in the next section.
void _pushSaved() {
Navigator.of(context).push(
);
}
Next, you'll add the MaterialPageRoute
and its builder. For now, add the code that generates the ListTile
rows. The divideTiles()
method of ListTile
adds horizontal spacing between each ListTile
. The divided
variable holds the final rows converted to a list by the convenience function, toList()
.
Add the code, as shown in the following code snippet:
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
// NEW lines from here...
builder: (BuildContext context) {
final tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved Suggestions'),
),
body: ListView(children: divided),
);
}, // ...to here.
),
);
}
}
The builder
property returns a Scaffold
containing the app bar for the new route named SavedSuggestions
. The body of the new route consists of a ListView
containing the ListTiles
rows. Each row is separated by a divider.
Hot reload the app. Favorite some of the selections and tap the list icon in the app bar. The new route appears containing the favorites. Note that the Navigator adds a "Back" button to the app bar. You did not have to explicitly implement
Navigator.pop
. Tap the back button to return to the home route.
iOS - Main route | iOS - Saved suggestions route |
Problems?
If your app isn't correctly running, then you can use the code at the following link to get back on track.
In this step, you'll modify the app's theme. The theme controls the look and feel of your app. You can either use the default theme, which is dependent on the physical device or emulator, or customize the theme to reflect your branding.
You can easily change an app's theme by configuring the ThemeData
class. The app uses the default theme, but you'll change the app's primary color to white.
Change the color in the
MyApp
class:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData( // Add the 3 lines from here...
primaryColor: Colors.white,
), // ... to here.
home: RandomWords(),
);
}
}
Hot reload the app. The entire background is now white, even the app bar.
As an exercise, use ThemeData
to change other aspects of the UI. The Colors
class in the Material library provides many color constants that you can play with. Hot reload makes experimenting with the UI quick and easy.
Android | iOS |
Problems?
If you've gotten off track, then use the code from the following link to see the code for the final app.
You wrote an interactive Flutter app that runs on iOS and Android by doing the following:
Learn more about the Flutter SDK from the following resources:
Other resources include: