This tutorial is based on one of Google's original Flutter codelabs, now no longer available although an archived version can be found here
In this codelab, you'll create a simple Flutter app. If you are familiar with object-oriented code and basic programming concepts such as variables, loops, and conditionals, you can complete this codelab. You don't need previous experience with Dart or mobile programming.
What you'll learn in part 1
How to write a Flutter app that looks natural on both iOS and Android.
Basic structure of a Flutter app.
Finding and using packages to extend functionality.
Using hot reload for a quicker development cycle.
How to implement a stateful widget.
How to create an infinite, lazily loaded list.
You'll implement a simple mobile app that generates proposed names for a startup company. The user can select and unselect names, saving the best ones. The code lazily generates ten names at a time. As the user scrolls, new batches of names are generated. The user can scroll forever, with new names being continually generated.
The animated GIF shows how the app works at the completion of part 1:
Create a simple templated Flutter app. If you are unsure how to do this refer to the Getting Started guide. Name the project startup_namer (instead of myapp). You'll be modifying this starter app to create the finished app.
In these codelabs, you'll mostly be editing lib/main.dart, where the Dart code lives.
Replace the contents of lib/main.dart.
Delete all of the code from lib/main.dart. Replace with the following code, which displays "Hello World" in the center of the screen.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Welcome to Flutter',
home: new Scaffold(
appBar: new AppBar(
title: const Text('Welcome to Flutter'),
),
body: const Center(
child: const Text('Hello World'),
),
),
);
}
}
Run the app. You should see something like this.
=>
) notation, Use fat arrow notation for one-line functions or methods.Center
widget containing a Text
child widget. The Center
widget aligns its widget subtree to the center of the screen.In this step, you'll start using an open-source package named english_words, which contains a few thousand of the most used English words plus some utility functions.
You can find the english_words package, as well as many other open source packages, on pub.dartlang.org.
The pubspec
file manages the assets for a Flutter app. In pubspec.yaml, append english_words: ^4.0.0 (english_words 4.0.0 or higher) to the dependencies list:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
english_words: ^4.0.0 # add this line
While viewing the pubspec in Android Studio's editor view, click Packages get. This pulls the package into your project. You should see the following in the console:
flutter packages get
Running "flutter packages get" in startup_namer...
Process finished with exit code 0
In lib/main.dart, import the new package:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart'; // Add this line.
As you type, Android Studio gives you suggestions for libraries to import. It then renders the import string in gray, letting you know that the imported library is unused (so far).
Next, you'll use the english_words package to generate the text instead of using the string "Hello World".
Make the changes shown below:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final wordPair = new WordPair.random(); // Add this line.
return new MaterialApp(
title: 'Welcome to Flutter',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Welcome to Flutter'),
),
body: new Center( // Change "const" to "new".
//child: const Text('Hello World'), // Replace this text...
child: new Text(wordPair.asPascalCase), // With this text.
),
),
);
}
}
If you didn't change Center
to "new Center", the type system will warn that the Center
object should not be typed as a constant (const
), because its child Text
object is no longer a constant. Therefore, both Center
and Text
must create instances using new.
If the app is running, use the hot reload button to update the running app. Each time you click hot reload or save the project, you should see a different word pair, chosen at random, in the running app. This is because the word pairing is generated inside the build method, which is run each time the MaterialApp
requires rendering, or when toggling the Platform in Flutter Inspector.
Stateless widgets are immutable, meaning that their properties can't change - all values are final.
Stateful widgets maintain state that might change during the lifetime of the widget. Implementing a stateful widget requires at least two classes: 1. a StatefulWidget
class that creates an instance of 2. a State
class. The StatefulWidget
class is, itself, immutable, but the State class persists over the lifetime of the widget.
In this step, you'll add a stateful widget, RandomWords
, which creates its State
class, RandomWordsState
. You'll then use RandomWords as a child inside the existing MyApp
stateless widget.
Create a minimal state class. It can go anywhere in the file outside of MyApp
. Add the following text:
class RandomWordsState extends State<RandomWords> {
// TODO Add build method
}
Notice the declaration State<RandomWords>
. This indicates that we're using the generic State class specialized for use with RandomWords. Most of the app's logic and state resides here-it maintains the state for the RandomWords widget. This class saves the generated word pairs, which grows infinitely as the user scrolls, and favorite word pairs (in part 2), as the user adds or removes them from the list by toggling the heart icon.
RandomWordsState depends on the RandomWords class. You'll add that next.
Add the stateful RandomWords
widget to main.dart. The RandomWords
widget does little else besides creating its State
class:
class RandomWords extends StatefulWidget {
@override
RandomWordsState createState() => new RandomWordsState();
}
After adding the State
class, the IDE complains that the class is missing a build method. Next, you'll add a basic build method that generates the word pairs by moving the word generation code from MyApp to RandomWordsState
.
Add the build method to RandomWordsState
, as shown below:
class RandomWordsState extends State<RandomWords> {
@override // Add from this line ...
Widget build(BuildContext context) {
final WordPair wordPair = new WordPair.random();
return new Text(wordPair.asPascalCase);
} // ... to this line.
}
Remove the word generation code from MyApp
by making the changes below:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final WordPair wordPair = new WordPair.random(); // Delete this line.
return new MaterialApp(
title: 'Welcome to Flutter',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Welcome to Flutter'),
),
body: new Center(
//child: new Text(wordPair.asPascalCase), // Change this line to...
child: new RandomWords(), // ... this line.
),
),
);
}
}
Hot reload the app. The app should behave as before, displaying a word pairing each time you hot reload or save the app.
In this step, you'll expand RandomWordsState
to generate and display a list of word pairings. As the user scrolls, the list (displayed in a ListView
widget) grows infinitely. ListView's
builder factory constructor allows you to build a list view lazily, on demand.
Add a _suggestions list to the RandomWordsState
class for saving suggested word pairings. Also, add a _biggerFont
variable for making the font size larger.
class RandomWordsState extends State<RandomWords> {
// Add the next two lines.
final List<WordPair> _suggestions = <WordPair>[];
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
...
}
Next, you'll add a _buildSuggestions()
function to the RandomWordsState
class. This method will build the ListView
that displays the suggested word pairing.
The ListView
class provides a builder property, itemBuilder
, that's a factory builder and callback function specified as an anonymous function. Two parameters are passed to the function the BuildContext
, and the row iterator, i. The iterator begins at 0 and increments each time the function is called once for every suggested word pairing. This model allows the suggestion list to grow infinitely as the user scrolls.
Add the entire _buildSuggestions
function, shown below, to the RandomWordsState
class (delete the comments, if you prefer):
Widget _buildSuggestions() {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
// The itemBuilder callback is called once per suggested
// word pairing, and places each suggestion into a ListTile
// row. For even rows, the function adds a ListTile row for
// the word pairing. For odd rows, the function adds a
// Divider widget to visually separate the entries. Note that
// the divider may be difficult to see on smaller devices.
itemBuilder: (BuildContext _context, int i) {
// Add a one-pixel-high divider widget before each row
// in the ListView.
if (i.isOdd) {
return new Divider();
}
// The syntax "i ~/ 2" divides i by 2 and returns an
// integer result.
// For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
// This calculates the actual number of word pairings
// in the ListView,minus the divider widgets.
final int index = i ~/ 2;
// If you've reached the end of the available word
// pairings...
if (index >= _suggestions.length) {
// ...then generate 10 more and add them to the
// suggestions list.
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
}
);
}
The _buildSuggestions
function calls _buildRow
once per word pair. This function displays each new pair in a ListTile
, which will allow you to make the rows more attractive in part 2.
Add a _buildRow
function to RandomWordsState
:
Widget _buildRow(WordPair pair) {
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
Update the build method for RandomWordsState
to use _buildSuggestions()
, rather than directly calling the word generation library. (Scaffold implements the basic Material Design visual layout.)
@override
Widget build(BuildContext context) {
//final wordPair = new WordPair.random(); // Delete these...
//return new Text(wordPair.asPascalCase); // ... two lines.
return new Scaffold ( // Add from here...
appBar: new AppBar(
title: new Text('Startup Name Generator'),
),
body: _buildSuggestions(),
); // ... to here.
}
Update the build method for MyApp
, changing the title, and changing the home to be a RandomWords
widget.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
home: new RandomWords(),
);
}
Restart the app. You should see a list of word pairings, no matter how far you scroll.
You have completed this codelab! Next time in Part 2 you will extend this app to: