diff --git a/_sass/_base.scss b/_sass/_base.scss index 9c28e29db01..15d8f08d7b9 100644 --- a/_sass/_base.scss +++ b/_sass/_base.scss @@ -63,6 +63,16 @@ figcaption { font-size: $small-font-size; } +.right-figure { + float: right; + margin: 0 0 5px 20px; + + @include media-query($on-palm) { + margin: inherit; + float: inherit; + } +} + /** diff --git a/_sass/_customstyles.scss b/_sass/_customstyles.scss index 4df5db3a707..edbf7c11d89 100644 --- a/_sass/_customstyles.scss +++ b/_sass/_customstyles.scss @@ -815,3 +815,12 @@ table th code { .alert__banner { margin-top: 3em; } + +.h2-like { + // This is for headlines that should look like H2 but shouldn't appear + // in the TOC. + font-size: 24px; + font-weight: 300; + line-height: 29px; + margin-bottom: 15px; +} diff --git a/get-started/codelab/images/AndroidStudioIcon.png b/get-started/codelab/images/AndroidStudioIcon.png new file mode 100644 index 00000000000..1b06c04ccf2 Binary files /dev/null and b/get-started/codelab/images/AndroidStudioIcon.png differ diff --git a/get-started/codelab/images/green-run.png b/get-started/codelab/images/green-run.png new file mode 100644 index 00000000000..87fa67078d8 Binary files /dev/null and b/get-started/codelab/images/green-run.png differ diff --git a/get-started/codelab/images/hello-world-screenshot.png b/get-started/codelab/images/hello-world-screenshot.png new file mode 100644 index 00000000000..33cf13e1445 Binary files /dev/null and b/get-started/codelab/images/hello-world-screenshot.png differ diff --git a/get-started/codelab/images/hot-reload-button.png b/get-started/codelab/images/hot-reload-button.png new file mode 100644 index 00000000000..fdf0c6dfa25 Binary files /dev/null and b/get-started/codelab/images/hot-reload-button.png differ diff --git a/get-started/codelab/images/startup-namer-app.gif b/get-started/codelab/images/startup-namer-app.gif new file mode 100644 index 00000000000..431d75415df Binary files /dev/null and b/get-started/codelab/images/startup-namer-app.gif differ diff --git a/get-started/codelab/images/step2-screenshot.png b/get-started/codelab/images/step2-screenshot.png new file mode 100644 index 00000000000..e203858214f Binary files /dev/null and b/get-started/codelab/images/step2-screenshot.png differ diff --git a/get-started/codelab/images/step3-screenshot.png b/get-started/codelab/images/step3-screenshot.png new file mode 100644 index 00000000000..1da824b6db2 Binary files /dev/null and b/get-started/codelab/images/step3-screenshot.png differ diff --git a/get-started/codelab/images/step4-screenshot.png b/get-started/codelab/images/step4-screenshot.png new file mode 100644 index 00000000000..4afdb9dd16c Binary files /dev/null and b/get-started/codelab/images/step4-screenshot.png differ diff --git a/get-started/codelab/images/step5-screenshot.png b/get-started/codelab/images/step5-screenshot.png new file mode 100644 index 00000000000..9de8ee1e743 Binary files /dev/null and b/get-started/codelab/images/step5-screenshot.png differ diff --git a/get-started/codelab/images/step6a-screenshot.png b/get-started/codelab/images/step6a-screenshot.png new file mode 100644 index 00000000000..3724e2a6685 Binary files /dev/null and b/get-started/codelab/images/step6a-screenshot.png differ diff --git a/get-started/codelab/images/step6b-screenshot.png b/get-started/codelab/images/step6b-screenshot.png new file mode 100644 index 00000000000..7a622b3a7a5 Binary files /dev/null and b/get-started/codelab/images/step6b-screenshot.png differ diff --git a/get-started/codelab/images/step7-themes.png b/get-started/codelab/images/step7-themes.png new file mode 100644 index 00000000000..c17da8a4a30 Binary files /dev/null and b/get-started/codelab/images/step7-themes.png differ diff --git a/get-started/codelab/index.md b/get-started/codelab/index.md new file mode 100644 index 00000000000..b1ec1150f16 --- /dev/null +++ b/get-started/codelab/index.md @@ -0,0 +1,1002 @@ +--- +layout: tutorial +title: Write Your First Flutter App +permalink: /get-started/codelab/ +--- + +
+ Animated GIF of the app that you will be building. +
+ +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. + +{% comment %} +TODO: (maybe, but later) +- Retake screenshots on the Android emulator? (Tao) +- Somehow cross link from code to text so people can restart + and find their place more easily? (Tao) +{% endcomment %} + +* TOC +{:toc} + +

What you'll build

+ +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 generates ten names at a time. +As the user scrolls, new batches of names are generated. +The user can tap the list icon in the upper right of the app bar +to move to a new route that lists only the favorited names. + +The animated GIF shows how the finished app works. + +
+ + What you'll learn: + +* 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. +* How to create and navigate to a second screen. +* How to change the look of an app using Themes. + +
+ +
+ + What you'll use: + +You'll need to install the following: + + + +See [Flutter Installation and Setup](/setup/) for information on how +to set up your environment. + +
+ +# Step 1: Create the starting Flutter app + +Create a simple, templated Flutter app, using the instructions in +[Getting Started with your first Flutter app.](/getting-started/) +Name the project **startup_namer** (instead of _myapp_). +You’ll be modifying this starter app to create the finished app. + +In this codelab, you'll mostly be editing **lib/main.dart**, +where the Dart code lives. + + + +
    + +
  1. Replace 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. + +{% prettify dart %} +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: new Text('Welcome to Flutter'), + ), + body: new Center( + child: new Text('Hello World'), + ), + ), + ); + } +} +{% endprettify %} +
  2. + +
  3. Run the app. You should see the following screen. + +
    screenshot of hello world app
    +
  4. +
+ +

Observations

+ + +{% comment %} + Removing this for now. A) It might be confusing and B) the code as shown here is wrong. +
  • Moving the “hello world” text into a separate widget, + HelloWorld, results in an identical widget tree as the code above. + (This code is informational only. You are starting with the Hello + World code above.) + + + {% prettify dart %} + import 'package: flutter/material.dart'; + + class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + 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('Hello World') + ), + ), + ); + } + } + {% endprettify %} + + Update with this code: + + class HelloWorld extends StatelessWidget { + @override + Widget build(BuildContext context) { + return new Center( + child: new Text('Hello World'), + ); + } + } +
  • +{% endcomment %} + +--- + +# Step 2: Use an external package + +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](https://pub.dartlang.org/packages/english_words) +package, as well as many other open source packages, on +[pub.dartlang.org](https://pub.dartlang.org/flutter/). + +
      + +
    1. The pubspec file manages the assets for a Flutter app. + In **pubspec.yaml**, add **english_words** (3.1.0 or higher) + to the dependencies list. + The new line is highlighted below: + + +{% prettify dart %} +dependencies: + flutter: + sdk: flutter + + cupertino_icons: ^0.1.0 + [[highlight]]english_words: ^3.1.0[[/highlight]] +{% endprettify %} +
    2. + +
    3. While viewing the pubspec in Android Studio's editor view, + click **Packages get** upper right. This pulls the package into + your project. You should see the following in the console: + + +{% prettify dart %} +flutter packages get +Running "flutter packages get" in startup_namer... +Process finished with exit code 0 +{% endprettify %} +
    4. + +
    5. In **lib/main.dart**, add the import for `english_words`, + as shown in the highlighted line: + + +{% 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). +
    6. + +
    7. Use the English words package to generate the text instead of + using the string "Hello World". + + + +Make the following changes, as highlighted below: + + +{% prettify dart %} +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) { + [[highlight]]final wordPair = new WordPair.random();[[/highlight]] + 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([[highlight]]'Hello World'[[/highlight]]), // Replace the highlighted text... + child: new Text([[highlight]]wordPair.asPascalCase[[/highlight]]), // With this highlighted text. + ), + ), + ); + } +} +{% endprettify %} +
    8. + +
    9. If the app is running, use the hot reload button + (lightning bolt icon) + 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 app is hot loaded or saved. + +
      screenshot at completion of second step
      +
    10. + +
    + +

    Problems?

    + +If your app isn't running correctly, look for typos. If needed, +use the code at the following links to get back on track. + +* [**pubspec.yaml**](https://gist.githubusercontent.com/Sfshaza/bb51e3b7df4ebbf3dfd02a4a38db2655/raw/57c25b976ec34d56591cb898a3df0b320e903b99/pubspec.yaml) +(The **pubspec.yaml** file won't change again.) +* [**lib/main.dart**](https://gist.githubusercontent.com/Sfshaza/bb51e3b7df4ebbf3dfd02a4a38db2655/raw/57c25b976ec34d56591cb898a3df0b320e903b99/main.dart) + +--- + +# Step 3: Add a Stateful widget + +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. The State class will eventually +maintain the proposed and favorite word pairs for the widget. + +
      +
    1. Add the stateful RandomWords widget to main.dart. + It can go anywhere in the file, outside of MyApp, but the solution + places it at the bottom of the file. The RandomWords widget does little + else besides creating its State class: + + +{% prettify dart %} +class RandomWords extends StatefulWidget { + @override + createState() => new RandomWordsState(); +} +{% endprettify %} +
    2. + +
    3. Add the RandomWordsState class. Most of the + app’s code resides in this class, which maintains the state for the + RandomWords widget. This class will save the generated word pairs, + which grow infinitely as the user scrolls, and also favorite + word pairs, as the user adds or removes them from the list by + toggling the heart icon. + +You’ll build this class bit by bit. To begin, create a minimal +class by adding the highlighted text: + + +{% prettify dart %} +[[highlight]]class RandomWordsState extends State {[[/highlight]] +[[highlight]]}[[/highlight]] +{% endprettify %} +
    4. + +
    5. 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 RandomWordState, as shown +by the highlighted text: + + +{% prettify dart %} +class RandomWordsState extends State { + [[highlight]]@override[[/highlight]] + [[highlight]]Widget build(BuildContext context) {[[/highlight]] + [[highlight]]final wordPair = new WordPair.random();[[/highlight]] + [[highlight]]return(new Text(wordPair.asPascalCase));[[/highlight]] + [[highlight]]}[[/highlight]] +} +{% endprettify %} +
    6. + +
    7. Remove the word generation code from MyApp by making + the highlighted changes below: + + +{% prettify dart %} +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + [[strike]]final wordPair = new WordPair.random();[[/strike]] // 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 [[highlight]]Text(wordPair.asPascalCase),[[/highlight]] // Change the highlighted text to... + child: new [[highlight]]RandomWords(),[[/highlight]] // ... this highlighted text + ), + ), + ); + } +} +{% endprettify %} +
    8. + +
    + +Restart the app. If you try to hot reload, you might see a warning: + +{% prettify sh %} +Reloading... +Not all changed program elements ran during view reassembly; consider +restarting. +{% endprettify %} + +It may be a false positive, but consider restarting in order to make sure +that your changes are reflected in the app's UI. + +The app should behave as before, displaying a word +pairing each time you hot reload or save the app. + +
    screenshot at completion of third step
    + +

    Problems?

    + +If your app isn't running correctly, you can use the code +at the following link to get back on track. + +* [**lib/main.dart**](https://gist.githubusercontent.com/Sfshaza/d7f13ddd8888556232476be8578efe40/raw/329c397b97309ce99f834bf70ebb90778baa5cfe/main.dart) + +--- + +# Step 4: Create an infinite scrolling ListView + +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. + +
      + +
    1. Add a `_suggestions` list to the RandomWordsState +class for saving suggested word pairings. Note that the variable begins +with an underscore (`_`). Prefixing an identifier with an underscore enforces +privacy in the Dart language. + +Also, add a `biggerFont` variable for making the font size larger. + + +{% prettify dart %} +class RandomWordsState extends State { + [[highlight]]final _suggestions = [];[[/highlight]] + + [[highlight]]final _biggerFont = const TextStyle(fontSize: 18.0);[[/highlight]] + ... +} +{% endprettify %} +
    2. + +
    3. Add a `_buildSuggestions()` function to the RandomWordsState +class. This method builds the ListView that displays the suggested word +pairing. + +The ListView class provides a builder property, `itemBuilder`, +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 suggested list to grow infinitely as the user scrolls. + +Add the highlighted lines below: + + +{% prettify dart %} +class RandomWordsState extends State { + ... + [[highlight]]Widget _buildSuggestions() {[[/highlight]] + [[highlight]]return new 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 odd rows, the function adds two rows to the ListView - one + // for the word pairing, and a second holds a Divider widget to + // visually separate the entries. + [[highlight]]itemBuilder: (context, i) {[[/highlight]] + // Add a one-pixel-high divider widget before each row in theListView. + [[highlight]]if (i.isOdd) return new 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 %} +
    4. + +
    5. The `_buildSuggestions` function calls `_buildRow` once per +word pair. This function displays each new pair in a ListTile, +which allows you to make the rows more attractive in the next step. + +Add a `_buildRow` function to RandomWordsState: + + +{% prettify dart %} +class RandomWordsState extends State { + ... + + [[highlight]]Widget _buildRow(WordPair pair) {[[/highlight]] + [[highlight]]return new ListTile([[/highlight]] + [[highlight]]title: new Text([[/highlight]] + [[highlight]]pair.asPascalCase,[[/highlight]] + [[highlight]]style: _biggerFont,[[/highlight]] + [[highlight]])[[/highlight]] + [[highlight]]);[[/highlight]] + [[highlight]]}[[/highlight]] +} +{% endprettify %} +
    6. + +
    7. Update the build method for RandomWordsState to use +`_buildSuggestions()`, rather than directly calling the word +generation library. Make the highlighted changes: + + +{% prettify dart %} +class RandomWordsState extends State { + ... + @override + Widget build(BuildContext context) { + [[strike]]final wordPair = new WordPair.random();[[/strike]] // Delete these two lines. + [[strike]]Return(new Text(wordPair.asPascalCase));[[/strike]] + [[highlight]]return new Scaffold ([[/highlight]] + [[highlight]]appBar: new AppBar([[/highlight]] + [[highlight]]title: new Text('Startup Name Generator'),[[/highlight]] + [[highlight]]),[[/highlight]] + [[highlight]]body: _buildSuggestions(),[[/highlight]] + [[highlight]]);[[/highlight]] + } + ... +} +{% endprettify %} +
    8. + +
    9. Update the build method for MyApp. + Remove the Scaffold and AppBar instances from MyApp. + These will be managed by RandomWordsState, which makes it easier to + change the name of the route in the app bar as the user + navigates from one screen to another in the next step. + +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 new MaterialApp([[/highlight]] + [[highlight]]title: 'Startup Name Generator',[[/highlight]] + [[highlight]]home: new RandomWords(),[[/highlight]] + [[highlight]]);[[/highlight]] + [[highlight]]}[[/highlight]] +} +{% endprettify %} +
    10. + +
    + +Restart the app. You should see a list of word pairings. Scroll down +as far as you want and you will continue to see new word pairings. + +
    screenshot at completion of fourth step
    + +

    Problems?

    + +If your app isn't running correctly, you can use the code +at the following link to get back on track. + +* [**lib/main.dart**](https://gist.githubusercontent.com/Sfshaza/d6f9460a04d3a429eb6ac0b0f07da564/raw/34fe240f4122435c871bb737708ee0357741801c/main.dart) + +--- + +# Step 5: Add interactivity + +In this step, you'll add tappable heart icons to each row. +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. + +
      +
    1. 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. + + +{% prettify dart %} +class RandomWordsState extends State { + final _suggestions = []; + + [[highlight]]final _saved = new Set();[[/highlight]] + + final _biggerFont = const TextStyle(fontSize: 18.0); + ... +} +{% endprettify %} +
    2. + +
    3. In the `_buildRow` function, add an `alreadySaved` + check to ensure that a word pairing hasn't already been added to + favorites. + + +{% prettify dart %} + Widget _buildRow(WordPair pair) { + [[highlight]]final alreadySaved = _saved.contains(pair);[[/highlight]] + ... + } +{% endprettify %} +
    4. + +
    5. Also in `_buildRow()`, add heart-shaped icons to the + ListTiles to enable favoriting. Later, you'll add the ability to + interact with the heart icons. + +Add the highlighted lines below: + + +{% prettify dart %} + Widget _buildRow(WordPair pair) { + final alreadySaved = _saved.contains(pair); + return new ListTile( + title: new Text( + pair.asPascalCase, + style: _biggerFont, + ), + [[highlight]]trailing: new Icon([[/highlight]] + [[highlight]]alreadySaved ? Icons.favorite : Icons.favorite_border,[[/highlight]] + [[highlight]]color: alreadySaved ? Colors.red : null,[[/highlight]] + [[highlight]]),[[/highlight]] + ); + } +{% endprettify %} +
    6. + +
    7. Restart the app. You should now see open hearts on each row, + but they aren't yet interactive. +
    8. + +
    9. Make the hearts tappable in the `_buildRow` function. + If a word entry has already been added to favorites, tapping it again + removes it from favorites. When the heart has been tapped, the function + calls `setState()` to notify the framework that state has changed. + +Add the highlighted lines: + + +{% prettify dart %} + Widget _buildRow(WordPair pair) { + final alreadySaved = _saved.contains(pair); + return new ListTile( + title: new Text( + pair.asPascalCase, + style: _biggerFont, + ), + trailing: new Icon( + alreadySaved ? Icons.favorite : Icons.favorite_border, + color: alreadySaved ? Colors.red : null, + ), + [[highlight]]onTap: () {[[/highlight]] + [[highlight]]setState([[/highlight]] + [[highlight]]() {[[/highlight]] + [[highlight]]if (alreadySaved) {[[/highlight]] + [[highlight]]_saved.remove(pair);[[/highlight]] + [[highlight]]} else {[[/highlight]] + [[highlight]]_saved.add(pair);[[/highlight]] + [[highlight]]}[[/highlight]] + [[highlight]]},[[/highlight]] + [[highlight]]);[[/highlight]] + [[highlight]]},[[/highlight]] + ); + } +{% endprettify %} +
    10. +
    + + + +Hot reload the app. You should be able to tap any row to favorite, or unfavorite, +the entry. Note that tapping a row generates an implicit ink splash animation +that emanates from the heart icon. + +
    screenshot at completion of 5th step
    + +

    Problems?

    + +If your app isn't running correctly, you can use the code +at the following link to get back on track. + +* [**lib/main.dart**](https://gist.githubusercontent.com/Sfshaza/936ce0059029a8c6e88aaa826a3789cd/raw/a3065d5c681a81eff32f75a9cd5f4d9a5b24f9ff/main.dart) + +--- + +# Step 6: Navigate to a new screen + +In this step, you'll add a new screen (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. + +
      +
    1. 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 favorites items is pushed to the Navigator, + displaying the icon. + + + +Add the icon and its corresponding action to the build method: + + +{% prettify dart %} +class RandomWordsState extends State { + ... + @override + Widget build(BuildContext context) { + return new Scaffold( + appBar: new AppBar( + title: new Text('Startup Name Generator'), + [[highlight]]actions: [[[/highlight]] + [[highlight]]new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),[[/highlight]] + [[highlight]]],[[/highlight]] + ), + body: _buildSuggestions(), + ); + } + ... +} +{% endprettify %} +
    2. + +
    3. Add a `_pushSaved()` function to the RandomWordsState class. + + +{% prettify dart %} +class RandomWordsState extends State { + ... + [[highlight]]void _pushSaved() {[[/highlight]] + [[highlight]]}[[/highlight]] +} +{% endprettify %} + +Hot reload the app. The list icon appears in the app bar. +Tapping it does nothing yet, because the `_pushSaved` function is empty. +
    4. + +
    5. When the user taps the list icon in the app bar, + build a route and push it to the Navigator's stack. + This 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. + +Add the call to Navigator.push, as shown by the highlighted code, +which pushes the route to the Navigator's stack. + + +{% prettify dart %} + [[highlight]]void _pushSaved() {[[/highlight]] + [[highlight]]Navigator.of(context).push([[/highlight]] + [[highlight]]);[[/highlight]] + [[highlight]]}[[/highlight]] +{% endprettify %} +
    6. + +
    7. 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 convienice function, `toList()`. + + +{% prettify dart %} + void _pushSaved() { + Navigator.of(context).push( + [[highlight]]new MaterialPageRoute([[/highlight]] + [[highlight]]builder: (context) {[[/highlight]] + [[highlight]]final tiles = _saved.map([[/highlight]] + [[highlight]](pair) {[[/highlight]] + [[highlight]]return new ListTile([[/highlight]] + [[highlight]]title: new Text([[/highlight]] + [[highlight]]pair.asPascalCase,[[/highlight]] + [[highlight]]style: _biggerFont,[[/highlight]] + [[highlight]]),[[/highlight]] + [[highlight]]);[[/highlight]] + [[highlight]]},[[/highlight]] + [[highlight]]);[[/highlight]] + [[highlight]]final divided = ListTile[[/highlight]] + [[highlight]].divideTiles([[/highlight]] + [[highlight]]context: context,[[/highlight]] + [[highlight]]tiles: tiles,[[/highlight]] + [[highlight]])[[/highlight]] + [[highlight]].toList();[[/highlight]] + [[highlight]]},[[/highlight]] + [[highlight]]),[[/highlight]] + ); + } +{% endprettify %} +
    8. + +
    9. The builder property returns a Scaffold. + From the Material library, this widget provides an app bar for the + new route with the name, "Saved Suggestions". The body consists of a + ListView with the ListTiles. + +Add the highlisted code below: + + +{% prettify dart %} + void _pushSaved() { + Navigator.of(context).push( + new MaterialPageRoute( + builder: (context) { + final tiles = _saved.map( + (pair) { + return new ListTile( + title: new Text( + pair.asPascalCase, + style: _biggerFont, + ), + ); + }, + ); + final divided = ListTile + .divideTiles( + context: context, + tiles: tiles, + ) + .toList(); + + [[highlight]]return new Scaffold([[/highlight]] + [[highlight]]appBar: new AppBar([[/highlight]] + [[highlight]]title: new Text('Saved Suggestions'),[[/highlight]] + [[highlight]]),[[/highlight]] + [[highlight]]body: new ListView(children: divided),[[/highlight]] + [[highlight]]);[[/highlight]] + }, + ), + ); + } +{% endprettify %} +
    10. + +
    11. 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 didn't have to explicitly implement Navigator.pop. + Tap the back button to return to the home route. +
    12. +
    + +
    screenshot at completion of 6th stepsecond route
    + +

    Problems?

    + +If your app isn't running correctly, you can use the code +at the following link to get back on track. + +* [**lib/main.dart**](https://gist.github.com/Sfshaza/bc5547e112e4dc3a1aa87afdf917caeb) + +--- +# Step 7: Change the UI using Themes + +In this final step, you'll play with the app's theme. The +_theme_ controls the look and feel of your app. You can use +the default theme, which is dependent on the physical device +or emulator, or you can customize the theme to reflect your branding. + +
      +
    1. You can easily change an app's theme by configuring + the ThemeData class. Your app currently uses the default theme, + but you'll be changing the primary color to be white. + +Change the app's theme to white by adding the highlighted code to MyApp: + + +{% prettify dart %} +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return new MaterialApp( + title: 'Startup Name Generator', + [[highlight]]theme: new ThemeData([[/highlight]] + [[highlight]]primaryColor: Colors.white,[[/highlight]] + [[highlight]]),[[/highlight]] + home: new RandomWords(), + ); + } +} +{% endprettify %} +
    2. + +
    3. Hot reload the app. Notice that the entire background is white, +even the app bar. +
    4. + +
    5. As an exercise for the reader, use +[ThemeData](https://docs.flutter.io/flutter/material/ThemeData-class.html) +to change other aspects of the UI. The +[Colors](https://docs.flutter.io/flutter/material/Colors-class.html) +class in the Material library provides many color constants you can play with, +and hot reload makes experimenting with the UI quick and easy. +
    6. +
    + +
    screenshot at completion of 7th step
    + +

    Problems?

    + +If you've gotten off track, use the code from the following link +to see the code for the final app. + +* [**lib/main.dart**](https://gist.githubusercontent.com/Sfshaza/c07c91a4061fce4b5eacaaf4d82e4993/raw/4001a72c0133b97c8e16bdeb3195ca03525696bd/main.dart) + +--- + +# Well done! + +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, adding interactivity to your app. +* Created a lazily loaded, infinite scrolling list displayed with a + ListView and ListTiles. +* Created a route and added logic for moving between the home route + and the new route. +* Learned about changing the look of your app's UI using Themes. + +Here are some resources you might find useful: + +{% comment %} +Once the "Flutter for React" doc (etc) are done, add here. +{% endcomment %} + +* [Flutter API docs](https://docs.flutter.io/) +* [Building Layouts in Flutter](/tutorials/layout/) tutorial +* [Add Interactivity](/tutorials/interactive/) tutorial +* [Flutter Cookbook](/cookbook/) +* [From Java to Dart](https://codelabs.developers.google.com/codelabs/from-java-to-dart/#0) codelab + +You can learn more about Flutter at [flutter.io,](https://flutter.io/) +and Dart at [www.dartlang.org.](https://www.dartlang.org/) +