Unverified Commit 262f12b4 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Remove remaining "## Sample code" segments, and fix the snippet generator. (#27793)

This converts all remaining "## Sample code" segments into snippets, and fixes
the snippet generator to handle multiple snippets in the same dartdoc block
properly.

I also generated, compiled, and ran each of the existing application samples,
and fixed them up to be more useful and/or just run without errors.

This PR fixes these problems with examples:

1. Switching tabs in a snippet now works if there is more than one snippet in
   a single dartdoc block.
2. Generation of snippet code now works if there is more than one snippet.
3. Contrast of text and links in the code sample block has been improved to
   recommended levels.
4. Added five new snippet templates, including a "freeform" template to make
   it possible to show examples that need to change the app instantiation.
5. Fixed several examples to run properly, a couple by adding the "Scaffold"
   widget to the template, a couple by just fixing their code.
6. Fixed visual look of some of the samples when they run by placing many
   samples inside of a Scaffold.
7. In order to make it easier to run locally, changed the sample analyzer to
   remove the contents of the supplied temp directory before running, since
   having files that hang around is problematic (only a problem when running
   locally with the `--temp` argument).
8. Added a `SampleCheckerException` class, and handle sample checking
   exceptions more gracefully.
9. Deprecated the old "## Sample code" designation, and added enforcement for
   the deprecation.
10. Removed unnecessary `new` from templates (although they never appeared in
   the samples thanks to dartfmt, but still).

Fixes #26398
Fixes #27411
parent 7862bef1
This diff is collapsed.
......@@ -18,8 +18,7 @@
/// blabla 0.0, the penzance blabla is blabla not blabla at all. Bla the blabla
/// 1.0, the blabla is blabla blabla blabla an blabla blabla.
///
/// ### Sample code
///
/// {@tool sample}
/// Bla blabla blabla some [Text] when the `_blabla` blabla blabla is true, and
/// blabla it when it is blabla:
///
......@@ -29,9 +28,9 @@
/// child: const Text('Poor wandering ones!'),
/// )
/// ```
/// {@end-tool}
///
/// ## Sample code
///
/// {@tool sample}
/// Bla blabla blabla some [Text] when the `_blabla` blabla blabla is true, and
/// blabla finale blabla:
///
......@@ -41,3 +40,4 @@
/// child: const Text('Poor wandering ones!'),
/// )
/// ```
/// {@end-tool}
......@@ -17,9 +17,9 @@ void main() {
..removeWhere((String line) => line.startsWith('Analyzer output:'));
expect(process.exitCode, isNot(equals(0)));
expect(stderrLines, <String>[
'known_broken_documentation.dart:27:9: new Opacity(',
'known_broken_documentation.dart:26:9: new Opacity(',
'>>> Unnecessary new keyword (unnecessary_new)',
'known_broken_documentation.dart:39:9: new Opacity(',
'known_broken_documentation.dart:38:9: new Opacity(',
'>>> Unnecessary new keyword (unnecessary_new)',
'',
'Found 1 sample code errors.',
......
/* Styles for handling custom code snippets */
.snippet-container {
background-color: #45aae8;
background-color: #2372a3;
padding: 10px;
overflow: auto;
}
......@@ -30,8 +30,21 @@
color: white;
}
.snippet-description a:link {
color: #c7fcf4;
}
.snippet-description a:visited {
color: #c7dbfc;
}
.snippet-description a:hover {
color: white;
}
.snippet-description a:active {
color: #80b0fc;
}
.snippet-buttons button {
background-color: #45aae8;
background-color: #2372a3;
border-style: none;
color: white;
padding: 10px 24px;
......@@ -82,7 +95,7 @@
height: 28px;
width: 28px;
transition: .3s ease;
background-color: #45aae8;
background-color: #2372a3;
}
.copy-button {
......@@ -102,7 +115,7 @@
.copy-image {
opacity: 0.65;
color: #45aae8;
color: #2372a3;
font-size: 28px;
padding-top: 4px;
}
......@@ -2,15 +2,10 @@
* Scripting for handling custom code snippets
*/
const shortSnippet = 'shortSnippet';
const longSnippet = 'longSnippet';
var visibleSnippet = shortSnippet;
/**
* Shows the requested snippet. Values for "name" can be "shortSnippet" or
* "longSnippet".
* Shows the requested snippet, and stores the current state in visibleSnippet.
*/
function showSnippet(name) {
function showSnippet(name, visibleSnippet) {
if (visibleSnippet == name) return;
if (visibleSnippet != null) {
var shown = document.getElementById(visibleSnippet);
......@@ -39,6 +34,7 @@ function showSnippet(name) {
if (button != null) {
button.setAttributeNode(selectedAttribute);
}
return visibleSnippet;
}
// Finds a sibling to given element with the given id.
......@@ -64,8 +60,8 @@ function supportsCopying() {
// Copies the text inside the currently visible snippet to the clipboard, or the
// given element, if any.
function copyTextToClipboard(element) {
if (element == null) {
var elementSelector = '#' + visibleSnippet + ' .language-dart';
if (typeof element === 'string') {
var elementSelector = '#' + element + ' .language-dart';
element = document.querySelector(elementSelector);
if (element == null) {
console.log(
......
......@@ -7,8 +7,9 @@ snippets.
This takes code in dartdocs, like this:
```dart
/// The following is a skeleton of a stateless widget subclass called `GreenFrog`:
/// {@tool snippet --template="stateless_widget"}
/// The following is a skeleton of a stateless widget subclass called `GreenFrog`.
/// ```dart
/// class GreenFrog extends StatelessWidget {
/// const GreenFrog({ Key key }) : super(key: key);
///
......@@ -17,6 +18,7 @@ This takes code in dartdocs, like this:
/// return Container(color: const Color(0xFF2DBD3A));
/// }
/// }
/// ```
/// {@end-tool}
```
......
{@inject-html}
<div class="snippet-buttons">
<button id="shortSnippetButton" onclick="showSnippet(shortSnippet);" selected>Sample</button>
<button id="longSnippetButton" onclick="showSnippet(longSnippet);">Sample in an App</button>
<script>var visibleSnippet{{serial}} = "shortSnippet{{serial}}";</script>
<button id="shortSnippet{{serial}}Button"
onclick="visibleSnippet{{serial}} = showSnippet('shortSnippet{{serial}}', visibleSnippet{{serial}});"
selected>Sample</button>
<button id="longSnippet{{serial}}Button"
onclick="visibleSnippet{{serial}} = showSnippet('longSnippet{{serial}}', visibleSnippet{{serial}});">Sample in an App</button>
</div>
<div class="snippet-container">
<div class="snippet" id="shortSnippet">
<div class="snippet" id="shortSnippet{{serial}}">
{{description}}
<div class="copyable-container">
<button class="copy-button-overlay copy-button" title="Copy to clipboard"
onclick="copyTextToClipboard();">
onclick="copyTextToClipboard(visibleSnippet{{serial}});">
<i class="material-icons copy-image">assignment</i>
</button>
<pre class="language-{{language}}"><code class="language-{{language}}">{{code}}</code></pre>
</div>
</div>
<div class="snippet" id="longSnippet" hidden>
<div class="snippet" id="longSnippet{{serial}}" hidden>
<div class="snippet-description">To create a sample project with this code snippet, run:<br/>
<span class="snippet-create-command">flutter create --sample={{id}} mysample</span>
</div>
<div class="copyable-container">
<button class="copy-button-overlay copy-button" title="Copy to clipboard"
onclick="copyTextToClipboard();">
onclick="copyTextToClipboard(visibleSnippet{{serial}});">
<i class="material-icons copy-image">assignment</i>
</button>
<pre class="language-{{language}}"><code class="language-{{language}}">{{app}}</code></pre>
......
{@inject-html}
<div class="snippet-buttons">
<button id="shortSnippetButton" selected>Sample</button>
<button id="shortSnippet{{serial}}Button" selected>Sample</button>
</div>
<div class="snippet-container">
<div class="snippet">{{description}}
......
......@@ -49,6 +49,11 @@ additional burden, since all code will also be compiled to be sure it compiles).
The templates available for using as an argument to the snippets tool are as
follows:
- [`freeform`](freeform.tmpl) :
This is a simple template for which you provide everything. It has no code of
its own, just the sections for `imports`, `main`, and `preamble`. You must
provide the `main` section in order to have a `main()`.
- [`stateful_widget`](stateful_widget.tmpl) :
The default code block will be placed as the body of the `State` object of a
StatefulWidget subclass. Because the default code block is placed as the body
......@@ -58,8 +63,26 @@ follows:
function calls are not allowed in the preamble. It also has an `imports`
section to import additional packages. Please only import things that are part
of flutter or part of default dependencies for a `flutter create` project.
It creates a WidgetsApp around the child stateful widget.
- [`stateless_widget`](stateless_widget.tmpl) :
Identical to the `stateful_widget` template, except that the default code
block is inserted as the return value for a pre-existing `build` function in a
StatelessWidget, instead of being at the class level.
block is inserted as the `build` function in a
StatelessWidget. There is no need to include the @override before the build
funciton (the template adds this for you).
- [`stateful_widget_material`](stateful_widget_material.tmpl) : Similar to
`stateful_widget`, except that it imports the material library, and uses
a MaterialApp instead of WidgetsApp.
- [`stateless_widget_material`](stateless_widget_material.tmpl) : Similar to
`stateless_widget`, except that it imports the material library, and uses
a MaterialApp instead of WidgetsApp.
- [`stateful_widget_scaffold`](stateful_widget_scaffold.tmpl) : Similar to
`stateful_widget_material`, except that it wraps the stateful widget with a
Scaffold.
- [`stateless_widget_scaffold`](stateless_widget_scaffold.tmpl) : Similar to
`stateless_widget_material`, except that it wraps the stateless widget with a
Scaffold.
// Flutter code sample for {{id}}
{{description}}
{{code-imports}}
{{code-main}}
{{code-preamble}}
{{code}}
// Flutter code sample for {{id}}
{{description}}
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
{{code-imports}}
void main() => runApp(new MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Code Sample for {{id}}',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyStatefulWidget(),
return WidgetsApp(
title: 'Flutter Code Sample',
home: MyStatefulWidget(),
color: const Color(0xffffffff),
);
}
}
{{code-preamble}}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
{{code}}
}
// Flutter code sample for {{id}}
{{description}}
import 'package:flutter/material.dart';
{{code-imports}}
void main() => runApp(new MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
{{code-preamble}}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
{{code}}
}
// Flutter code sample for {{id}}
{{description}}
import 'package:flutter/material.dart';
{{code-imports}}
void main() => runApp(new MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: Text(_title)),
body: MyStatefulWidget(),
),
);
}
}
{{code-preamble}}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
{{code}}
}
// Flutter code sample for {{id}}
{{description}}
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
{{code-imports}}
void main() => runApp(new MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Code Sample for {{id}}',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyStatelessWidget(),
return WidgetsApp(
title: 'Flutter Code Sample',
builder: (BuildContext context, Widget navigator) {
return MyStatelessWidget();
},
color: const Color(0xffffffff),
);
}
}
{{code-preamble}}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return {{code}};
}
{{code}}
}
// Flutter code sample for {{id}}
{{description}}
import 'package:flutter/material.dart';
{{code-imports}}
void main() => runApp(new MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
{{code-preamble}}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({Key key}) : super(key: key);
@override
{{code}}
}
// Flutter code sample for {{id}}
{{description}}
import 'package:flutter/material.dart';
{{code-imports}}
void main() => runApp(new MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: Text(_title)),
body: MyStatelessWidget(),
),
);
}
}
{{code-preamble}}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
MyStatelessWidget({Key key}) : super(key: key);
@override
{{code}}
}
......@@ -11,6 +11,7 @@ import 'package:platform/platform.dart';
import 'configuration.dart';
import 'snippets.dart';
const String _kSerialOption = 'serial';
const String _kElementOption = 'element';
const String _kHelpOption = 'help';
const String _kInputOption = 'input';
......@@ -75,6 +76,11 @@ void main(List<String> argList) {
defaultsTo: environment['ELEMENT_NAME'],
help: 'The name of the element that this snippet belongs to.',
);
parser.addOption(
_kSerialOption,
defaultsTo: environment['INVOCATION_INDEX'],
help: 'A unique serial number for this snippet tool invocation.',
);
parser.addFlag(
_kHelpOption,
defaultsTo: false,
......@@ -117,6 +123,7 @@ void main(List<String> argList) {
final String packageName = args[_kPackageOption] != null && args[_kPackageOption].isNotEmpty ? args[_kPackageOption] : null;
final String libraryName = args[_kLibraryOption] != null && args[_kLibraryOption].isNotEmpty ? args[_kLibraryOption] : null;
final String elementName = args[_kElementOption] != null && args[_kElementOption].isNotEmpty ? args[_kElementOption] : null;
final String serial = args[_kSerialOption] != null && args[_kSerialOption].isNotEmpty ? args[_kSerialOption] : null;
final List<String> id = <String>[];
if (args[_kOutputOption] != null) {
id.add(path.basename(path.basenameWithoutExtension(args[_kOutputOption])));
......@@ -130,10 +137,13 @@ void main(List<String> argList) {
if (elementName != null) {
id.add(elementName);
}
if (serial != null) {
id.add(serial);
}
if (id.isEmpty) {
errorExit('Unable to determine ID. At least one of --$_kPackageOption, '
'--$_kLibraryOption, --$_kElementOption, or the environment variables '
'PACKAGE_NAME, LIBRARY_NAME, or ELEMENT_NAME must be non-empty.');
'--$_kLibraryOption, --$_kElementOption, -$_kSerialOption, or the environment variables '
'PACKAGE_NAME, LIBRARY_NAME, ELEMENT_NAME, or INVOCATION_INDEX must be non-empty.');
}
}
......@@ -149,6 +159,7 @@ void main(List<String> argList) {
'sourceLine': environment['SOURCE_LINE'] != null
? int.tryParse(environment['SOURCE_LINE'])
: null,
'serial': serial,
'package': packageName,
'library': libraryName,
'element': elementName,
......
......@@ -31,7 +31,8 @@ class SnippetGenerator {
SnippetGenerator({Configuration configuration})
: configuration = configuration ??
// Flutter's root is four directories up from this script.
Configuration(flutterRoot: Directory(Platform.environment['FLUTTER_ROOT'] ?? path.canonicalize(path.join(path.dirname(path.fromUri(Platform.script)), '..', '..', '..')))) {
Configuration(flutterRoot: Directory(Platform.environment['FLUTTER_ROOT']
?? path.canonicalize(path.join(path.dirname(path.fromUri(Platform.script)), '..', '..', '..')))) {
this.configuration.createOutputDirectory();
}
......@@ -72,10 +73,10 @@ class SnippetGenerator {
// Remove any leading/trailing empty comment lines.
// We don't want to remove ALL empty comment lines, only the ones at the
// beginning and the end.
while (description.last == '// ') {
while (description.isNotEmpty && description.last == '// ') {
description.removeLast();
}
while (description.first == '// ') {
while (description.isNotEmpty && description.first == '// ') {
description.removeAt(0);
}
return description.join('\n').trim();
......@@ -98,7 +99,7 @@ class SnippetGenerator {
///
/// Takes into account the [type] and doesn't substitute in the id and the app
/// if not a [SnippetType.application] snippet.
String interpolateSkeleton(SnippetType type, List<_ComponentTuple> injections, String skeleton) {
String interpolateSkeleton(SnippetType type, List<_ComponentTuple> injections, String skeleton, Map<String, Object> metadata) {
final List<String> result = <String>[];
const HtmlEscape htmlEscape = HtmlEscape();
String language;
......@@ -128,12 +129,13 @@ class SnippetGenerator {
'language': language ?? 'dart',
}..addAll(type == SnippetType.application
? <String, String>{
'serial': metadata['serial'].toString() ?? '0',
'id':
injections.firstWhere((_ComponentTuple tuple) => tuple.name == 'id').mergedContent,
'app':
htmlEscape.convert(injections.firstWhere((_ComponentTuple tuple) => tuple.name == 'app').mergedContent),
}
: <String, String>{'id': '', 'app': ''});
: <String, String>{'serial': '', 'id': '', 'app': ''});
return skeleton.replaceAllMapped(RegExp('{{(${substitutions.keys.join('|')})}}'), (Match match) {
return substitutions[match[1]];
});
......@@ -180,6 +182,16 @@ class SnippetGenerator {
return file.readAsStringSync(encoding: Encoding.getByName('utf-8'));
}
String _addLineNumbers(String app) {
final StringBuffer buffer = StringBuffer();
int count = 0;
for (String line in app.split('\n')) {
count++;
buffer.writeln('${count.toString().padLeft(5, ' ')}: $line');
}
return buffer.toString();
}
/// The main routine for generating snippets.
///
/// The [input] is the file containing the dartdoc comments (minus the leading
......@@ -220,7 +232,7 @@ class SnippetGenerator {
try {
app = formatter.format(app);
} on FormatterException catch (exception) {
stderr.write('Code to format:\n$app\n');
stderr.write('Code to format:\n${_addLineNumbers(app)}\n');
errorExit('Unable to format snippet app template: $exception');
}
......@@ -250,6 +262,6 @@ class SnippetGenerator {
break;
}
final String skeleton = _loadFileAsUtf8(configuration.getHtmlSkeletonFile(type));
return interpolateSkeleton(type, snippetData, skeleton);
return interpolateSkeleton(type, snippetData, skeleton, metadata);
}
}
......@@ -32,7 +32,7 @@ import 'package:flutter/foundation.dart';
/// look at the physical key to make sure that regardless of the character the
/// key produces, you got the key that is in that location on the keyboard.
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_scaffold}
/// This example shows how to detect if the user has selected the logical "Q"
/// key.
///
......@@ -270,7 +270,7 @@ class LogicalKeyboardKey extends Diagnosticable {
/// looking for "the key next next to the TAB key", since on a French keyboard,
/// the key next to the TAB key has an "A" on it.
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_scaffold}
/// This example shows how to detect if the user has selected the physical key
/// to the right of the CAPS LOCK key.
///
......
......@@ -228,25 +228,28 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// For less common operations, consider using a [PopupMenuButton] as the
/// last action.
///
/// {@tool snippet --template=stateless_widget}
/// {@tool snippet --template=stateless_widget_material}
///
/// This sample shows adding an action to an [AppBar] that opens a shopping cart.
///
/// ```dart
/// Scaffold(
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: Text('Hello World'),
/// title: Text('Ready, Set, Shop!'),
/// actions: <Widget>[
/// IconButton(
/// icon: Icon(Icons.shopping_cart),
/// tooltip: 'Open shopping cart',
/// onPressed: () {
/// // ...
/// // Implement navigation to shopping cart page here...
/// print('Shopping cart opened.');
/// },
/// ),
/// ],
/// ),
/// )
/// );
/// }
/// ```
/// {@end-tool}
final List<Widget> actions;
......
......@@ -68,8 +68,7 @@ enum BottomNavigationBarType {
/// case it's assumed that each item will have a different background color
/// and that background color will contrast well with white.
///
/// ## Sample Code
///
/// {@tool snippet --template=stateful_widget_material}
/// This example shows a [BottomNavigationBar] as it is used within a [Scaffold]
/// widget. The [BottomNavigationBar] has three [BottomNavigationBarItem]
/// widgets and the [currentIndex] is set to index 1. The color of the selected
......@@ -78,21 +77,19 @@ enum BottomNavigationBarType {
/// the [Scaffold].
///
/// ```dart
/// class MyHomePage extends StatefulWidget {
/// MyHomePage({Key key}) : super(key: key);
///
/// @override
/// _MyHomePageState createState() => _MyHomePageState();
/// }
///
/// class _MyHomePageState extends State<MyHomePage> {
/// int _selectedIndex = 1;
/// final _widgetOptions = [
/// static const List<Widget> _widgetOptions = const <Widget>[
/// Text('Index 0: Home'),
/// Text('Index 1: Business'),
/// Text('Index 2: School'),
/// ];
///
/// void _onItemTapped(int index) {
/// setState(() {
/// _selectedIndex = index;
/// });
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
......@@ -114,14 +111,8 @@ enum BottomNavigationBarType {
/// ),
/// );
/// }
///
/// void _onItemTapped(int index) {
/// setState(() {
/// _selectedIndex = index;
/// });
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
......
......@@ -19,13 +19,14 @@ import 'theme.dart';
/// some text describing a musical, and the other with buttons for buying
/// tickets or listening to the show.](https://flutter.github.io/assets-for-api-docs/assets/material/card.png)
///
/// {@tool snippet --template=stateless_widget}
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// This sample shows creation of a [Card] widget that shows album information
/// and two actions.
///
/// ```dart
/// Center(
/// Widget build(BuildContext context) {
/// return Center(
/// child: Card(
/// child: Column(
/// mainAxisSize: MainAxisSize.min,
......@@ -52,27 +53,38 @@ import 'theme.dart';
/// ],
/// ),
/// ),
/// )
/// );
/// }
/// ```
/// {@end-tool}
///
/// Sometimes the primary action area of a card is the card itself. Cards can be
/// one large touch target that shows a detail screen when tapped.
///
/// {@tool snippet --template=stateless_widget}
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// This sample shows creation of a [Card] widget that can be tapped. When
/// tapped this [Card]'s [InkWell] displays an "ink splash" that fills the
/// entire card.
///
/// ```dart
/// Card(
/// Widget build(BuildContext context) {
/// return Center(
/// child: Card(
/// child: InkWell(
/// splashColor: Colors.blue.withAlpha(30),
/// onTap: () { /* ... */ },
/// onTap: () {
/// print('Card tapped.');
/// },
/// child: Container(
/// width: 300,
/// height: 100,
/// child: Text('A card that can be tapped'),
/// ),
/// )
/// ),
/// ),
/// );
/// }
/// ```
///
/// {@end-tool}
......
......@@ -151,7 +151,7 @@ abstract class DeletableChipAttributes {
/// that the user tapped the delete button. In order to delete the chip, you
/// have to do something similar to the following sample:
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_scaffold}
///
/// This sample shows how to use [onDeleted] to remove an entry when the
/// delete button is tapped.
......@@ -207,7 +207,7 @@ abstract class DeletableChipAttributes {
/// ```dart
/// @override
/// Widget build(BuildContext context) {
/// return CastList();
/// return Center(child: CastList());
/// }
/// ```
/// {@end-tool}
......
......@@ -485,7 +485,7 @@ class DropdownButtonHideUnderline extends InheritedWidget {
/// dropdown's value. It should also call [State.setState] to rebuild the
/// dropdown with the new value.
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_scaffold}
///
/// This sample shows a `DropdownButton` whose value is one of
/// "One", "Two", "Free", or "Four".
......
......@@ -37,7 +37,7 @@ const double _kMinButtonSize = 48.0;
/// requirements in the Material Design specification. The [alignment] controls
/// how the icon itself is positioned within the hit region.
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_scaffold}
///
/// This sample shows an `IconButton` that uses the Material icon "volume_up" to
/// increase the volume.
......@@ -83,7 +83,7 @@ const double _kMinButtonSize = 48.0;
/// the underlying [Material] along with the splash and highlight
/// [InkResponse] contributed by descendant widgets.
///
/// {@tool snippet --template=stateless_widget}
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the icon button's background color is defined with an [Ink]
/// widget whose child is an [IconButton]. The icon button's filled background
......@@ -91,17 +91,25 @@ const double _kMinButtonSize = 48.0;
/// button is.
///
/// ```dart
/// Ink(
/// Widget build(BuildContext context) {
/// return Center(
/// child: Container(
/// child: Ink(
/// decoration: ShapeDecoration(
/// color: Colors.purple,
/// color: Colors.lightBlue,
/// shape: CircleBorder(),
/// ),
/// child: IconButton(
/// icon: Icon(Icons.android),
/// color: Colors.white,
/// onPressed: () { print("filled background"); },
/// onPressed: () {
/// print("filled background");
/// },
/// ),
/// ),
/// )
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
......
......@@ -31,13 +31,14 @@ import 'theme_data.dart';
/// Raised buttons have a minimum size of 88.0 by 36.0 which can be overidden
/// with [ButtonTheme].
///
/// {@tool snippet --template=stateless_widget}
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// This sample shows how to render a disabled RaisedButton, an enabled RaisedButton
/// and lastly a RaisedButton with gradient background.
///
/// ```dart
/// Scaffold(
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: Center(
/// child: Column(
/// mainAxisSize: MainAxisSize.min,
......@@ -67,7 +68,8 @@ import 'theme_data.dart';
/// ],
/// ),
/// ),
/// )
/// );
/// }
/// ```
/// {@end-tool}
///
......
......@@ -26,11 +26,11 @@ import 'snack_bar.dart';
import 'theme.dart';
// Examples can assume:
// TabController tabController
// TabController tabController;
// void setState(VoidCallback fn) { }
// String appBarTitle
// int tabCount
// TickerProvider tickerProvider
// String appBarTitle;
// int tabCount;
// TickerProvider tickerProvider;
const FloatingActionButtonLocation _kDefaultFloatingActionButtonLocation = FloatingActionButtonLocation.endFloat;
const FloatingActionButtonAnimator _kDefaultFloatingActionButtonAnimator = FloatingActionButtonAnimator.scaling;
......@@ -668,8 +668,7 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// [ScaffoldState] for the current [BuildContext] via [Scaffold.of] and use the
/// [ScaffoldState.showSnackBar] and [ScaffoldState.showBottomSheet] functions.
///
/// {@tool snippet --template=stateful_widget}
///
/// {@tool snippet --template=stateful_widget_material}
/// This example shows a [Scaffold] with an [AppBar], a [BottomAppBar] and a
/// [FloatingActionButton]. The [body] is a [Text] placed in a [Center] in order
/// to center the text within the [Scaffold] and the [FloatingActionButton] is
......@@ -736,21 +735,21 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr
/// scaffold with a differently titled AppBar. It would be better to add a
/// listener to the [TabController] that updates the AppBar.
///
/// ## Sample Code
///
/// {@tool sample}
/// Add a listener to the app's tab controller so that the [AppBar] title of the
/// app's one and only scaffold is reset each time a new tab is selected.
///
/// ```dart
/// tabController = TabController(vsync: tickerProvider, length: tabCount)..addListener(() {
/// TabController(vsync: tickerProvider, length: tabCount)..addListener(() {
/// if (!tabController.indexIsChanging) {
/// setState(() {
/// // Rebuild the enclosing scaffold with a new AppBar title
/// appBarTitle = 'Tab ${tabController.index}';
/// });
/// }
/// });
/// })
/// ```
/// {@end-tool}
///
/// Although there are some use cases, like a presentation app that
/// shows embedded flutter content, where nested scaffolds are
......@@ -949,30 +948,68 @@ class Scaffold extends StatefulWidget {
/// The state from the closest instance of this class that encloses the given context.
///
/// Typical usage is as follows:
/// {@tool snippet --template=freeform}
/// Typical usage of the [Scaffold.of] function is to call it from within the
/// `build` method of a child of a [Scaffold].
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart main
/// void main() => runApp(MyApp());
/// ```
///
/// ```dart preamble
/// class MyApp extends StatelessWidget {
/// // This widget is the root of your application.
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// title: 'Flutter Code Sample for Scaffold.of.',
/// theme: ThemeData(
/// primarySwatch: Colors.blue,
/// ),
/// home: Scaffold(
/// body: MyScaffoldBody(),
/// appBar: AppBar(title: Text('Scaffold.of Example')),
/// ),
/// color: Colors.white,
/// );
/// }
/// }
/// ```
///
/// ```dart
/// class MyScaffoldBody extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return RaisedButton(
/// return Center(
/// child: RaisedButton(
/// child: Text('SHOW A SNACKBAR'),
/// onPressed: () {
/// Scaffold.of(context).showSnackBar(SnackBar(
/// content: Text('Hello!'),
/// ));
/// Scaffold.of(context).showSnackBar(
/// SnackBar(
/// content: Text('Have a snack!'),
/// ),
/// );
/// },
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// {@tool snippet --template=stateless_widget_material}
/// When the [Scaffold] is actually created in the same `build` function, the
/// `context` argument to the `build` function can't be used to find the
/// [Scaffold] (since it's "above" the widget being returned). In such cases,
/// the following technique with a [Builder] can be used to provide a new
/// scope with a [BuildContext] that is "under" the [Scaffold]:
/// [Scaffold] (since it's "above" the widget being returned in the widget
/// tree). In such cases, the following technique with a [Builder] can be used
/// to provide a new scope with a [BuildContext] that is "under" the
/// [Scaffold]:
///
/// ```dart
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
......@@ -987,7 +1024,7 @@ class Scaffold extends StatefulWidget {
/// child: Text('SHOW A SNACKBAR'),
/// onPressed: () {
/// Scaffold.of(context).showSnackBar(SnackBar(
/// content: Text('Hello!'),
/// content: Text('Have a snack!'),
/// ));
/// },
/// ),
......@@ -997,6 +1034,7 @@ class Scaffold extends StatefulWidget {
/// );
/// }
/// ```
/// {@end-tool}
///
/// A more efficient solution is to split your build function into several
/// widgets. This introduces a new context from which you can obtain the
......
......@@ -192,25 +192,26 @@ class Stepper extends StatefulWidget {
/// This callback which takes in a context and two functions,[onStepContinue]
/// and [onStepCancel]. These can be used to control the stepper.
///
/// ## Sample Code:
/// {@tool snippet --template=stateless_widget_scaffold}
/// Creates a stepper control with custom buttons.
///
/// ```dart
/// Stepper(
/// Widget build(BuildContext context) {
/// return Stepper(
/// controlsBuilder:
/// (BuildContext context, {VoidCallback onStepContinue, VoidCallback onStepCancel}) {
/// return Row(
/// children: <Widget>[
/// FlatButton(
/// onPressed: onStepContinue,
/// child: const Text('My Awesome Continue Message!'),
/// child: const Text('CONTINUE'),
/// ),
/// FlatButton(
/// onPressed: onStepCancel,
/// child: const Text('My Awesome Cancel Message!'),
/// child: const Text('CANCEL'),
/// ),
/// ],
/// ),
/// );
/// },
/// steps: const <Step>[
/// Step(
......@@ -228,8 +229,10 @@ class Stepper extends StatefulWidget {
/// ),
/// ),
/// ],
/// )
/// );
/// }
/// ```
/// {@end-tool}
final ControlsWidgetBuilder controlsBuilder;
@override
......
......@@ -19,6 +19,9 @@ import 'view.dart';
export 'package:flutter/gestures.dart' show HitTestResult;
// Examples can assume:
// dynamic context;
/// The glue between the render tree and the Flutter engine.
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
......@@ -175,26 +178,30 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
/// The current platform brightness can be queried either from a Flutter
/// binding, or from a [MediaQuery] widget.
///
/// ## Sample Code
///
/// Querying [Window.platformBrightness]:
/// {@tool sample}
/// Querying [Window.platformBrightness].
///
/// ```dart
/// final Brightness brightness = WidgetsBinding.instance.window.platformBrightness;
/// ```
/// {@end-tool}
///
/// Querying [MediaQuery] directly:
/// {@tool sample}
/// Querying [MediaQuery] directly.
///
/// ```dart
/// final Brightness brightness = MediaQuery.platformBrightnessOf(context);
/// ```
/// {@end-tool}
///
/// Querying [MediaQueryData]:
/// {@tool sample}
/// Querying [MediaQueryData].
///
/// ```dart
/// final MediaQueryData mediaQueryData = MediaQuery.of(context);
/// final Brightness brightness = mediaQueryData.platformBrightness;
/// ```
/// {@end-tool}
///
/// See [Window.onPlatformBrightnessChanged].
/// {@endtemplate}
......
......@@ -1550,11 +1550,21 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
/// describes how high the box is that this [SemanticsNode] occupies in three
/// dimensional space. The two other dimensions are defined by [rect].
///
/// ## Sample Code
///
/// {@tool sample}
/// The following code stacks three [PhysicalModel]s on top of each other
/// separated by none-zero elevations:
/// separated by non-zero elevations.
///
/// [PhysicalModel] C is elevated 10.0 above [PhysicalModel] B, which in turn
/// is elevated 5.0 above [PhysicalModel] A. The side view of this
/// constellation looks as follows:
///
/// ![A diagram illustrating the elevations of three PhysicalModels and their
/// corresponding SemanticsNodes.](https://flutter.github.io/assets-for-api-docs/assets/semantics/SemanticsNode.thickness.png)
///
/// In this example the [RenderObject]s for [PhysicalModel] C and B share one
/// [SemanticsNode] Y. Given the elevations of those [RenderObject]s, this
/// [SemanticsNode] has a [thickness] of 10.0 and an elevation of 5.0 over
/// its parent [SemanticsNode] X.
/// ```dart
/// PhysicalModel( // A
/// color: Colors.amber,
......@@ -1571,20 +1581,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
/// ),
/// ),
/// ),
/// );
/// )
/// ```
///
/// [PhysicalModel] C is elevated 10.0 above [PhysicalModel] B, which in turn
/// is elevated 5.0 above [PhysicalModel] A. The side view of this
/// constellation looks as follows:
///
/// ![A diagram illustrating the elevations of three PhysicalModels and their
/// corresponding SemanticsNodes.](https://flutter.github.io/assets-for-api-docs/assets/semantics/SemanticsNode.thickness.png)
///
/// In this example the [RenderObject]s for [PhysicalModel] C and B share one
/// [SemanticsNode] Y. Given the elevations of those [RenderObject]s, this
/// [SemanticsNode] has a [thickness] of 10.0 and an elevation of 5.0 over
/// its parent [SemanticsNode] X.
/// {@end-tool}
///
/// See also:
///
......
......@@ -32,7 +32,7 @@ import 'package:flutter/foundation.dart';
/// look at the physical key to make sure that regardless of the character the
/// key produces, you got the key that is in that location on the keyboard.
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_scaffold}
/// This example shows how to detect if the user has selected the logical "Q"
/// key.
///
......@@ -1655,7 +1655,7 @@ class LogicalKeyboardKey extends Diagnosticable {
/// looking for "the key next next to the TAB key", since on a French keyboard,
/// the key next to the TAB key has an "A" on it.
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_scaffold}
/// This example shows how to detect if the user has selected the physical key
/// to the right of the CAPS LOCK key.
///
......
......@@ -311,15 +311,15 @@ class SystemChrome {
/// If a particular style is not supported on the platform, selecting it will
/// have no effect.
///
/// ## Sample Code
///
/// {@tool sample}
/// ```dart
/// @override
/// Widget build(BuildContext context) {
/// SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
/// return /* ... */;
/// return Placeholder();
/// }
/// ```
/// {@end-tool}
///
/// For more complex control of the system overlay styles, consider using
/// an [AnnotatedRegion] widget instead of calling [setSystemUiOverlayStyle]
......@@ -329,7 +329,7 @@ class SystemChrome {
/// navigation bar and synthesize them into a single style. This can be used
/// to configure the system styles when an app bar is not used.
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_material}
/// The following example creates a widget that changes the status bar color
/// to a random value on Android.
///
......@@ -358,8 +358,7 @@ class SystemChrome {
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: AnnotatedRegion(
/// return AnnotatedRegion(
/// value: _currentStyle,
/// child: Center(
/// child: RaisedButton(
......@@ -367,9 +366,9 @@ class SystemChrome {
/// onPressed: _changeColor,
/// ),
/// ),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// See also:
......
......@@ -5046,7 +5046,7 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
/// If it has a child, this widget defers to the child for sizing behavior. If
/// it does not have a child, it grows to fit the parent instead.
///
/// {@tool snippet --template=stateful_widget}
/// {@tool snippet --template=stateful_widget_scaffold}
/// This example makes a [Container] react to being entered by a mouse
/// pointer, showing a count of the number of entries and exits.
///
......@@ -5081,11 +5081,7 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: Text('Hover Example'),
/// ),
/// body: Center(
/// return Center(
/// child: ConstrainedBox(
/// constraints: new BoxConstraints.tight(Size(300.0, 200.0)),
/// child: Listener(
......@@ -5110,7 +5106,6 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
/// ),
/// ),
/// ),
/// ),
/// );
/// }
/// ```
......
......@@ -594,20 +594,30 @@ class NavigatorObserver {
/// in this situation, but it's a real world example where nested [Navigator]s
/// are used.
///
/// #### Sample Code
///
/// {@tool snippet --template=freeform}
/// The following example demonstrates how a nested [Navigator] can be used to
/// present a standalone user registration journey.
///
/// Even though this example uses two [Navigator]s to demonstrate nested
/// [Navigator]s, a similar result is possible using only a single [Navigator].
///
/// Run this example with `flutter run --route=/signup` to start it with
/// the signup flow instead of on the home page.
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart main
/// void main() => runApp(new MyApp());
/// ```
///
/// ```dart
/// class MyApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// // ...some parameters omitted...
/// title: 'Flutter Code Sample for Navigator',
/// // MaterialApp contains our top-level Navigator
/// initialRoute: '/',
/// routes: {
......@@ -618,6 +628,65 @@ class NavigatorObserver {
/// }
/// }
///
/// class HomePage extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return DefaultTextStyle(
/// style: Theme.of(context).textTheme.display1,
/// child: Container(
/// color: Colors.white,
/// alignment: Alignment.center,
/// child: Text('Home Page'),
/// ),
/// );
/// }
/// }
///
/// class CollectPersonalInfoPage extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return DefaultTextStyle(
/// style: Theme.of(context).textTheme.display1,
/// child: GestureDetector(
/// onTap: () {
/// // This moves from the personal info page to the credentials page,
/// // replacing this page with that one.
/// Navigator.of(context)
/// .pushReplacementNamed('signup/choose_credentials');
/// },
/// child: Container(
/// color: Colors.lightBlue,
/// alignment: Alignment.center,
/// child: Text('Collect Personal Info Page'),
/// ),
/// ),
/// );
/// }
/// }
///
/// class ChooseCredentialsPage extends StatelessWidget {
/// const ChooseCredentialsPage({
/// this.onSignupComplete,
/// });
///
/// final VoidCallback onSignupComplete;
///
/// @override
/// Widget build(BuildContext context) {
/// return GestureDetector(
/// onTap: onSignupComplete,
/// child: DefaultTextStyle(
/// style: Theme.of(context).textTheme.display1,
/// child: Container(
/// color: Colors.pinkAccent,
/// alignment: Alignment.center,
/// child: Text('Choose Credentials Page'),
/// ),
/// ),
/// );
/// }
/// }
///
/// class SignUpPage extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
......@@ -656,6 +725,7 @@ class NavigatorObserver {
/// }
/// }
/// ```
/// {@end-tool}
///
/// [Navigator.of] operates on the nearest ancestor [Navigator] from the given
/// [BuildContext]. Be sure to provide a [BuildContext] below the intended
......
......@@ -80,11 +80,17 @@ import 'scrollable.dart';
/// with some remaining space to allocate as specified by its
/// [Column.mainAxisAlignment] argument.
///
/// In this example, the children are spaced out equally, unless there's no
/// more room, in which case they stack vertically and scroll.
/// {@tool snippet --template=stateless_widget}
/// In this example, the children are spaced out equally, unless there's no more
/// room, in which case they stack vertically and scroll.
///
/// When using this technique, [Expanded] and [Flexible] are not useful, because
/// in both cases the "available space" is infinite (since this is in a viewport).
/// The next section describes a technique for providing a maximum height constraint.
///
/// ```dart
/// LayoutBuilder(
/// Widget build(BuildContext context) {
/// return LayoutBuilder(
/// builder: (BuildContext context, BoxConstraints viewportConstraints) {
/// return SingleChildScrollView(
/// child: ConstrainedBox(
......@@ -97,12 +103,12 @@ import 'scrollable.dart';
/// children: <Widget>[
/// Container(
/// // A fixed-height child.
/// color: Colors.yellow,
/// color: const Color(0xff808000), // Yellow
/// height: 120.0,
/// ),
/// Container(
/// // Another fixed-height child.
/// color: Colors.green,
/// color: const Color(0xff008000), // Green
/// height: 120.0,
/// ),
/// ],
......@@ -110,12 +116,10 @@ import 'scrollable.dart';
/// ),
/// );
/// },
/// )
/// );
/// }
/// ```
///
/// When using this technique, [Expanded] and [Flexible] are not useful, because
/// in both cases the "available space" is infinite (since this is in a viewport).
/// The next section describes a technique for providing a maximum height constraint.
/// {@end-tool}
///
/// ### Expanding content to fit the viewport
///
......@@ -135,8 +139,21 @@ import 'scrollable.dart';
/// The widget that is to grow to fit the remaining space so provided is wrapped
/// in an [Expanded] widget.
///
/// This technique is quite expensive, as it more or less requires that the contents
/// of the viewport be laid out twice (once to find their intrinsic dimensions, and
/// once to actually lay them out). The number of widgets within the column should
/// therefore be kept small. Alternatively, subsets of the children that have known
/// dimensions can be wrapped in a [SizedBox] that has tight vertical constraints,
/// so that the intrinsic sizing algorithm can short-circuit the computation when it
/// reaches those parts of the subtree.
///
/// {@tool snippet --template=stateless_widget}
/// In this example, the column becomes either as big as viewport, or as big as
/// the contents, whichever is biggest.
///
/// ```dart
/// LayoutBuilder(
/// Widget build(BuildContext context) {
/// return LayoutBuilder(
/// builder: (BuildContext context, BoxConstraints viewportConstraints) {
/// return SingleChildScrollView(
/// child: ConstrainedBox(
......@@ -148,14 +165,14 @@ import 'scrollable.dart';
/// children: <Widget>[
/// Container(
/// // A fixed-height child.
/// color: Colors.yellow,
/// color: const Color(0xff808000), // Yellow
/// height: 120.0,
/// ),
/// Expanded(
/// // A flexible child that will grow to fit the viewport but
/// // still be at least as big as necessary to fit its contents.
/// child: Container(
/// color: Colors.blue,
/// color: const Color(0xff800000), // Red
/// height: 120.0,
/// ),
/// ),
......@@ -165,16 +182,10 @@ import 'scrollable.dart';
/// ),
/// );
/// },
/// )
/// );
/// }
/// ```
///
/// This technique is quite expensive, as it more or less requires that the contents
/// of the viewport be laid out twice (once to find their intrinsic dimensions, and
/// once to actually lay them out). The number of widgets within the column should
/// therefore be kept small. Alternatively, subsets of the children that have known
/// dimensions can be wrapped in a [SizedBox] that has tight vertical constraints,
/// so that the intrinsic sizing algorithm can short-circuit the computation when it
/// reaches those parts of the subtree.
/// {@end-tool}
///
/// See also:
///
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment