layout | title | permalink | image |
---|---|---|---|
tutorial |
Write Your First Flutter App, part 1 |
/get-started/codelab/ |
/get-started/codelab/images/step7-themes.png |
This is a guide to creating your first Flutter app. If you are familiar with object-oriented code and basic programming concepts such as variables, loops, and conditionals, you can complete this tutorial. You don’t need previous experience with Dart or mobile programming.
This guide is part 1 of a two-part codelab. You can find part 2 on Google Developers. Part 1 can also be found on Google Developers.
- TOC {:toc}
{:.no_toc}
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 names. As the user scrolls, more names are generated. There is no limit to how far a user can scroll.
The animated GIF shows how the app works at the completion of 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.
In part 2 of this codelab, you'll add interactivity, modify the app's theme, and add the ability to navigate to a new screen (called a route in Flutter).
You need two pieces of software to complete this lab: the Flutter SDK and an editor. This codelab assumes Android Studio, but you can use your preferred editor.
You can run this codelab using any of the following devices:
- A physical device (Android or iOS) connected to your computer and set to developer mode.
- The iOS simulator.
- The Android emulator.
Create a simple, templated Flutter app, using the instructions in Getting Started with your first Flutter app. Name the project startup_namer (instead of myapp).
{{site.alert.tip}} If you don't see "New Flutter Project" as an option in your IDE, make sure you have the plugins installed for Flutter and Dart. {{site.alert.end}}
In this codelab, 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(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Welcome to Flutter', home: Scaffold( appBar: AppBar( title: Text('Welcome to Flutter'), ), body: Center( child: Text('Hello World'), ), ), ); } }
{{site.alert.tip}} When pasting code into your app, indentation can become skewed. You can fix this automatically with the Flutter tools:
- Android Studio / IntelliJ IDEA: Right-click the code and select Reformat Code with dartfmt.
- VS Code: Right-click and select Format Document.
- Terminal: Run
flutter format <filename>
. {{site.alert.end}}
-
Run the app by clicking the green arrow in the IDE. You should see either Android or iOS output, depending on your device.
Android (left) and iOS (right){{site.alert.tip}} The first time you run on a physical device, it can take awhile to load. After this, you can use hot reload for quick updates. Save also performs a hot reload if the app is running. {{site.alert.end}}
{:.no_toc}
- This example creates a Material app. Material is a visual design language that is standard on mobile and the web. Flutter offers a rich set of Material widgets.
- The main method specifies fat arrow (
=>
) notation. Use fat arrow notation for one-line functions or methods. - The app extends
StatelessWidget
which makes the app itself a widget. In Flutter, almost everything is a widget, including alignment, padding, and layout. - The
Scaffold
widget, from the Material library, provides a default app bar, title, and a body property that holds the widget tree for the home screen. The widget subtree can be quite complex. - A widget’s main job is to provide a
build()
method that describes how to display the widget in terms of other, lower level widgets. - The body for this example consists of a
Center
widget containing aText
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 and dependencies for a Flutter app. In pubspec.yaml, add english_words (3.1.0 or higher) to the dependencies list. Add the highlighted line below:
{% prettify yaml %} dependencies: flutter: sdk: flutter
cupertino_icons: ^0.1.0 [[highlight]]english_words: ^3.1.0[[/highlight]]
{% endprettify %}
-
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
Performing
Packages get
also auto-generates thepubspec.lock
file with a list of all packages pulled into the project and their version numbers. -
In lib/main.dart, import the new package:
{% prettify dart %} import 'package:flutter/material.dart'; [[highlight]]import 'package:english_words/english_words.dart';[[/highlight]] {% endprettify %}
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).
-
Use the English words package to generate the text instead of using the string "Hello World".
Make the following changes:
{% prettify dart %} import 'package:flutter/material.dart'; import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { [[highlight]]final wordPair = WordPair.random();[[/highlight]] return MaterialApp( title: 'Welcome to Flutter', home: Scaffold( appBar: AppBar( title: Text('Welcome to Flutter'), ), body: Center( //child: Text([[highlight]]'Hello World'[[/highlight]]), // Replace the highlighted text... child: Text([[highlight]]wordPair.asPascalCase[[/highlight]]), // With this highlighted text. ), ), ); } } {% endprettify %}
{{site.alert.note}} "Pascal case" (also known as "upper camel case"), means that each word in the string, including the first one, begins with an uppercase letter. So, "uppercamelcase" becomes "UpperCamelCase". {{site.alert.end}}
-
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.
Android (left) and iOS (right)
{:.no_toc}
If your app is not running correctly, look for typos. If needed, use the code at the following links to get back on track.
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. Add the following to the bottom of
main.dart
:{% prettify dart %} class RandomWordsState extends State { // TODO Add build method } {% endprettify %}
Notice the declaration
State<RandomWords>
. This indicates that we're using the generic State class specialized for use withRandomWords
. Most of the app's logic and state resides here—it maintains the state for theRandomWords
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 theRandomWords
class. You'll add that next. -
Add the stateful
RandomWords
widget tomain.dart
. TheRandomWords
widget does little else beside creating its State class:{% prettify dart %} [[highlight]]class RandomWords extends StatefulWidget {[[/highlight]] [[highlight]]@override[[/highlight]] [[highlight]]RandomWordsState createState() => new RandomWordsState();[[/highlight]] [[highlight]]}[[/highlight]] {% endprettify %}
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
toRandomWordsState
. -
Add the
build()
method toRandomWordState
:{% prettify dart %} class RandomWordsState extends State { [[highlight]]@override[[/highlight]] [[highlight]]Widget build(BuildContext context) {[[/highlight]] [[highlight]]final wordPair = WordPair.random();[[/highlight]] [[highlight]]return Text(wordPair.asPascalCase);[[/highlight]] [[highlight]]}[[/highlight]] } {% endprettify %}
-
Remove the word generation code from
MyApp
:{% prettify dart %} class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { [[strike]]final wordPair = WordPair.random();[[/strike]] // Delete this line
return MaterialApp( title: 'Welcome to Flutter', home: Scaffold( appBar: AppBar( title: Text('Welcome to Flutter'), ), body: Center( //child: [[highlight]]Text(wordPair.asPascalCase),[[/highlight]] // Change the highlighted text to... child: [[highlight]]RandomWords(),[[/highlight]] // ... this highlighted text ), ), ); }
} {% endprettify %}
-
Restart the app. The app should behave as before, displaying a word pairing each time you hot reload or save the app.
{{site.alert.tip}} If you see the following warning on a hot reload, consider restarting the app:
Reloading...
Some program elements were changed during reload but did not run when
the view was reassembled; you may need to restart the app (by pressing "R")
for the changes to have an effect.
It may be a false positive, but restarting ensures that your changes are reflected in the app's UI. {{site.alert.end}}
{:.no_toc}
If your app is not running correctly, you can use the code at the following link to get back on track.
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 theRandomWordsState
class for saving suggested word pairings. Also, add abiggerFont
variable for making the font size larger.{{site.alert.tip}} Prefixing an identifier with an underscore enforces privacy in the Dart language. {{site.alert.end}}
{% prettify dart %} class RandomWordsState extends State { [[highlight]]final _suggestions = [];[[/highlight]]
[[highlight]]final _biggerFont = const TextStyle(fontSize: 18.0);[[/highlight]] ...
} {% endprettify %}
Next, you'll add a
_buildSuggestions()
function to theRandomwordsState
class. This method builds theListView
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—theBuildContext
, 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 suggested list to grow infinitely as the user scrolls. -
Add the entire
_buildSuggestions()
function, shown below, to theRandomWordsState
class (delete the comments, if you prefer).{% prettify dart %} class RandomWordsState extends State { ... [[highlight]]Widget _buildSuggestions() {[[/highlight]] [[highlight]]return ListView.builder([[/highlight]] [[highlight]]padding: const EdgeInsets.all(16.0),[[/highlight]] // 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. [[highlight]]itemBuilder: (context, i) {[[/highlight]] // Add a one-pixel-high divider widget before each row in theListView. [[highlight]]if (i.isOdd) return Divider();[[/highlight]]
// 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. [[highlight]]final index = i ~/ 2;[[/highlight]] // If you've reached the end of the available word pairings... [[highlight]]if (index >= _suggestions.length) {[[/highlight]] // ...then generate 10 more and add them to the suggestions list. [[highlight]]_suggestions.addAll(generateWordPairs().take(10));[[/highlight]] [[highlight]]}[[/highlight]] [[highlight]]return _buildRow(_suggestions[index]);[[/highlight]] [[highlight]]}[[/highlight]] [[highlight]]);[[/highlight]] [[highlight]]}[[/highlight]]
} {% endprettify %}
The
_buildSuggestions()
function calls_buildRow()
once per word pair. This function displays each new pair in aListTile
, which allows you to make the rows more attractive in the next step. -
Add a
_buildRow()
function toRandomWordsState
:{% prettify dart %} class RandomWordsState extends State { ...
[[highlight]]Widget _buildRow(WordPair pair) {[[/highlight]] [[highlight]]return ListTile([[/highlight]] [[highlight]]title: Text([[/highlight]] [[highlight]]pair.asPascalCase,[[/highlight]] [[highlight]]style: _biggerFont,[[/highlight]] [[highlight]]),[[/highlight]] [[highlight]]);[[/highlight]] [[highlight]]}[[/highlight]]
} {% endprettify %}
-
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.){% prettify dart %} class RandomWordsState extends State { ... @override Widget build(BuildContext context) { [[strike]]final wordPair = WordPair.random();[[/strike]] // Delete these two lines. [[strike]]return Text(wordPair.asPascalCase);[[/strike]] [[highlight]]return Scaffold ([[/highlight]] [[highlight]]appBar: AppBar([[/highlight]] [[highlight]]title: Text('Startup Name Generator'),[[/highlight]] [[highlight]]),[[/highlight]] [[highlight]]body: _buildSuggestions(),[[/highlight]] [[highlight]]);[[/highlight]] } ... } {% endprettify %}
-
Update the build method for MyApp, changing the title, and changing the home to be a RandomWords widget.
Replace the original method with the highlighted build method below:
{% prettify dart %} class MyApp extends StatelessWidget { @override [[highlight]]Widget build(BuildContext context) {[[/highlight]] [[highlight]]return MaterialApp([[/highlight]] [[highlight]]title: 'Startup Name Generator',[[/highlight]] [[highlight]]home: RandomWords(),[[/highlight]] [[highlight]]);[[/highlight]] [[highlight]]}[[/highlight]] } {% endprettify %}
-
Restart the app. You should see a list of word pairings no matter how far you scroll.
Android (left) and iOS (right)
{:.no_toc}
If your app is not running correctly, you can use the code at the following link to get back on track.
{:.no_toc}
The app from part 2Congratulations!
You've written an interactive Flutter app that runs on both iOS and Android. In this codelab, you've:
- Created a Flutter app from the ground up.
- Written Dart code.
- Leveraged an external, third-party library.
- Used hot reload for a faster development cycle.
- Implemented a stateful widget.
- Created a lazily loaded, infinite scrolling list.
If you would like to extend this app, proceed to part 2 on the Google Developers Codelabs site, where you add the following functionality:
- Implement interactivity by adding a clickable heart icon to save favorite pairings.
- Implement navigation to a new route by adding a new screen containing the saved favorites.
- Modify the theme color, making an all-white app.