From f957b97bedc4a558317926c18576a14bfe939d22 Mon Sep 17 00:00:00 2001 From: Shams Zakhour Date: Wed, 29 May 2019 10:06:16 -0700 Subject: [PATCH 1/6] General cleanup of the using packages page. --- pubspec.lock | 23 +- .../packages-and-plugins/using-packages.md | 202 +++++++++--------- 2 files changed, 109 insertions(+), 116 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 9feaaa31182..d240a9db0af 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "0.6.1" + version: "0.5.0" build_resolvers: dependency: transitive description: @@ -56,7 +56,7 @@ packages: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.3.4" build_runner_core: dependency: transitive description: @@ -144,7 +144,7 @@ packages: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.14.6" + version: "0.16.0" dart_style: dependency: transitive description: @@ -200,7 +200,7 @@ packages: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.13.4+2" + version: "0.14.0+2" http: dependency: transitive description: @@ -256,7 +256,7 @@ packages: name: linkcheck url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.8" logging: dependency: transitive description: @@ -375,7 +375,7 @@ packages: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "2.0.0" stream_transform: dependency: transitive description: @@ -411,13 +411,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" - utf: - dependency: transitive - description: - name: utf - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.0+5" vector_math: dependency: transitive description: @@ -438,7 +431,7 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.0.12" + version: "1.0.13" yaml: dependency: "direct dev" description: @@ -447,4 +440,4 @@ packages: source: hosted version: "2.1.15" sdks: - dart: ">=2.3.0-dev.0.1 <3.0.0" + dart: ">=2.2.0 <3.0.0" diff --git a/src/docs/development/packages-and-plugins/using-packages.md b/src/docs/development/packages-and-plugins/using-packages.md index 881e0d9b1d8..ebf07a1fb92 100644 --- a/src/docs/development/packages-and-plugins/using-packages.md +++ b/src/docs/development/packages-and-plugins/using-packages.md @@ -1,6 +1,6 @@ --- title: Using packages -description: Learn how to use packages in your Flutter app. +description: How to use packages in your Flutter app. --- Flutter supports using shared packages contributed by other developers @@ -8,21 +8,24 @@ to the Flutter and Dart ecosystems. This allows you to quickly build your app without having to develop everything from scratch. Existing packages enable many use cases, for example, making network requests -([`http`](/docs/cookbook/networking/fetch-data)), custom navigation/route handling -([`fluro`]({{site.pub-pkg}}/fluro)), integration with device -APIs (like [`url_launcher`]({{site.pub-pkg}}/url_launcher) & +([`http`](/docs/cookbook/networking/fetch-data)), +custom navigation/route handling ([`fluro`]({{site.pub-pkg}}/fluro)), +integration with device APIs +([`url_launcher`]({{site.pub-pkg}}/url_launcher) and [`battery`]({{site.pub-pkg}}/battery)), -and using third-party platform SDKs (like -[Firebase]({{site.github}}/flutter/plugins/blob/master/FlutterFire.md)). +and using third-party platform SDKs +([Firebase]({{site.github}}/flutter/plugins/blob/master/FlutterFire.md)). -If you are looking to develop a new package, please see -[developing packages](/docs/development/packages-and-plugins/developing-packages). - -If you are looking to add assets, images, or fonts, whether stored in -files or packages, please see [Assets & images](/docs/development/ui/assets-and-images). +If you are looking to develop a new package, see [developing +packages](/docs/development/packages-and-plugins/developing-packages). +If you are looking to add assets, images, or fonts, +whether stored in files or packages, +see [Adding assets and images](/docs/development/ui/assets-and-images). ## Using packages +The following section describes how to use existing published packages. + ### Searching for packages Packages are published to the [*Pub site*]({{site.pub}}). @@ -34,70 +37,69 @@ and supports searching among all published packages. ### Adding a package dependency to an app -To add a package 'css_colors' to an app: +To add the package, `css_colors`, to an app: 1. Depend on it - * Open the `pubspec.yaml` file located inside your app folder, and add - `css_colors:` under `dependencies`. + * Open the `pubspec.yaml` file located inside the app folder, + and add `css_colors:` under `dependencies`. 1. Install it * From the terminal: Run `flutter pub get`
**OR** - * From Android Studio/IntelliJ: Click 'Packages Get' in the action + * From Android Studio/IntelliJ: Click **Packages Get** in the action ribbon at the top of `pubspec.yaml` - * From VS Code: Click 'Get Packages' located in right side of the action + * From VS Code: Click **Get Packages** located in right side of the action ribbon at the top of `pubspec.yaml` 1. Import it - * Add a corresponding `import` statement in your Dart code. + * Add a corresponding `import` statement in the Dart code. 1. Stop and restart the app, if necessary * If the package brings platform-specific code (Java/Kotlin for Android, Swift/Objective-C for iOS), that code must be built into your app. - Hot reload and hot restart do this only for the Dart code of the - package, so you may have to do a full restart of the app to avoid + Hot reload and hot restart only update the Dart code, + so a full restart of the app may be required to avoid errors like `MissingPluginException` when using the package. -The -['Installing']({{site.pub-pkg}}/css_colors#-installing-tab-) +The [Installing]({{site.pub-pkg}}/css_colors#-installing-tab-) tab available on any package page on Pub is a handy reference for these steps. -For a complete example, see [CSS Colors example](#css-example) below. +For a complete example, see the [CSS Colors example](#css-example) below. ### Conflict resolution -Suppose you want to use `some_package` and `other_package` in your app `counter` -(or your own package), and both of these depend on `url_launcher`, but -in different versions. Then we have a potential conflict. The best way to avoid this -is for package authors to use [version +Suppose you want to use `some_package` and `another_package` in your app, +and both of these depend on `url_launcher`, but in different versions. +Then you have a potential conflict. +The best way to avoid this is for package authors to use [version ranges]({{site.dart-site}}/tools/pub/dependencies#version-constraints) rather than specific versions when specifying dependencies. ```yaml dependencies: - url_launcher: ^0.4.2 # Good, any 0.4.x with x >= 2 will do. - image_picker: '0.1.1' # Not so good, only 0.1.1 will do. + url_launcher: ^0.4.2 # Good, any 0.4.x version where x >= 2 works. + image_picker: '0.1.1' # Not so good, only version 0.1.1 works. ``` -If `some_package` declares the dependencies above and `other_package` -declares a compatible `url_launcher` dependency like `'0.4.5'` or `^0.4.0`, -`pub` is able to resolve the issue automatically. Similar -remarks apply to plugin packages' platform-specific dependencies on -[Gradle modules][] and/or [CocoaPods][]. +If `some_package` declares the dependencies above and `another_package` +declares a compatible `url_launcher` dependency like `'0.4.5'` or +`^0.4.0`, Pub resolves the issue automatically. +Platform-specific dependencies on [Gradle modules][] and/or [CocoaPods][] +are solved in a similar way. -Even if `some_package` and `other_package` declare incompatible versions for -`url_launcher`, it may still be that they actually use `url_launcher` in -compatible ways. Then the conflict can be dealt with by adding -a dependency override declaration to the `pubspec.yaml` file in `counter`, +Even if `some_package` and `another_package` declare incompatible versions +for `url_launcher`, they might actually use `url_launcher` in +compatible ways. In this situation, the conflict can be resolved by adding +a dependency override declaration to the app's `pubspec.yaml` file, forcing the use of a particular version. -Forcing the use of `url_launcher` version `0.4.3` in `counter/pubspec.yaml`: +To force the use of `url_launcher` version `0.4.3` in the app's `pubspec.yaml`: ```yaml dependencies: some_package: - other_package: + another_package: dependency_overrides: url_launcher: '0.4.3' ``` @@ -106,7 +108,8 @@ If the conflicting dependency is not itself a package, but an Android-specific library like `guava`, the dependency override declaration must be added to Gradle build logic instead. -Forcing the use of `guava` version `23.0` in `counter/android/build.gradle`: +To force the use of `guava` version `23.0` in the app's +`android/build.gradle` file: ```groovy configurations.all { @@ -123,34 +126,37 @@ CocoaPods does not currently offer dependency override functionality. ## Developing new packages -Should a package not be available for your specific use case, you can +If no package exists for your specific use case, you can [develop new custom packages](/docs/development/packages-and-plugins/developing-packages). -## Managing package dependencies & versions +## Managing package dependencies and versions + +To minimize the risk of version collisions, +specify a version range in the pubspec file. ### Package versions -All packages have a version number, specified in their `pubspec.yaml` file. -The current version of a package is displayed next to its name (for example, -see the -[url_launcher]({{site.pub-pkg}}/url_launcher) package), as +All packages have a version number, specified in the +package's `pubspec.yaml` file. The current version of a package +is displayed next to its name (for example, +see the [url_launcher]({{site.pub-pkg}}/url_launcher) package), as well as a list of all prior versions ([url_launcher versions]({{site.pub-pkg}}/url_launcher#-versions-tab-)). -When a package is added to `pubspec.yaml` using the shorthand form `plugin1:` -this is interpreted as `plugin1: any`, i.e. any version of the package may be -used. To ensure your app does not break when a package is updated, -we recommend specifying a version range using one of the following formats: +When a package is added to `pubspec.yaml`, the shorthand form `plugin1:` +means that any version of the plugin1 package may be used. +To ensure that the app doesn't break when a package is updated, +specify a version range using one of the following formats: * Range constraints: Specify a minimum and maximim version. For example: - ``` + ```yaml dependencies: url_launcher: '>=0.1.2 <0.2.0' ``` -* Range constraint with [*caret +* Range constraints with [*caret syntax*]({{site.dart-site}}/tools/pub/dependencies#caret-syntax) is similar to regular range constraints: @@ -159,46 +165,47 @@ we recommend specifying a version range using one of the following formats: collection: '^0.1.2' ``` -For additional details, see the [Pub versioning +For additional details, see the [package versioning guide]({{site.dart-site}}/tools/pub/versioning). ### Updating package dependencies -When you run `flutter pub get` ('Packages Get' in IntelliJ) for -the first time after adding a package, Flutter saves the concrete package -version found in the `pubspec.lock` +When running `flutter pub get` (**Packages Get** in IntelliJ) +for the first time after adding a package, +Flutter saves the concrete package version found in the `pubspec.lock` [lockfile]({{site.dart-site}}/tools/pub/glossary#lockfile). This ensures that you get the same version again if you, or another developer on your team, run `flutter pub get`. -If you want to upgrade to a new version of the package, +To upgrade to a new version of the package, for example to use new features in that package, run -`flutter pub upgrade` ('Upgrade dependencies' -in IntelliJ). This retrieves the highest available version of the package -that is allowed by the version constraint you have specified in +`flutter pub upgrade` (**Upgrade dependencies** in IntelliJ) +to retrieve the highest available version of the package +that is allowed by the version constraint specified in `pubspec.yaml`. ### Dependencies on unpublished packages -Packages can be used even when not published on Pub. For private plugins not -intended for public publishing, or for packages not yet ready for publishing, -additional dependency options are avaialble: +Packages can be used even when not published on the Pub site. +For private plugins, or for packages not ready for publishing, +additional dependency options are available: * **Path** dependency: A Flutter app can depend on a plugin via a file system `path:` dependency. The path can be either relative, or absolute. - For example, to depend on a plugin 'plugin1' located in a directory - next to the app, use this syntax: + For example, to depend on a plugin `plugin1` located in a directory + next to the app, use the following syntax: - ``` + ```yaml dependencies: plugin1: path: ../plugin1/ ``` * **Git** dependency: You can also depend on a package stored in a Git - repository. If the package is located in the root of the repo, use this - syntax: - ``` + repository. If the package is located at the root of the repo, + use the following syntax: + + ```yaml dependencies: plugin1: git: @@ -207,8 +214,9 @@ additional dependency options are avaialble: * **Git** dependency on a package in a folder: By default Pub assumes the package is located in the root of the Git repository. If that is not the - case, you can specify the location with the `path` argument. For example: - ``` + case, specify the location with the `path` argument. For example: + + ```yaml dependencies: package1: git: @@ -216,38 +224,35 @@ additional dependency options are avaialble: path: packages/package1 ``` - Finally, you can use the `ref` argument to pin the dependency to a + Finally, use the `ref` argument to pin the dependency to a specific git commit, branch, or tag. For more details, see - [Pub Dependencies]({{site.dart-site}}/tools/pub/dependencies). + [Package Dependencies]({{site.dart-site}}/tools/pub/dependencies). ## Examples +If you are new to using packages, the following examples walk +you through the necessary steps. + ### Example: Using the CSS Colors package {#css-example} The [`css_colors`]({{site.pub-pkg}}/css_colors) package -defines color constants for the CSS colors, allowing you to use them +defines color constants for CSS colors, so they can be used wherever the Flutter framework expects the `Color` type. To use this package: 1. Create a new project called 'cssdemo' -1. Open `pubspec.yaml`, and replace: - ``` - dependencies: - flutter: - sdk: flutter - ``` - with: +1. Open `pubspec.yaml`, and add the `css-colors` dependency: - ``` + ```yaml dependencies: flutter: sdk: flutter css_colors: ^1.0.0 ``` -1. Run `flutter pub get` in the terminal, or click 'Packages get' in +1. Run `flutter pub get` in the terminal, or click **Packages get** in IntelliJ 1. Open `lib/main.dart` and replace its full contents with: @@ -277,29 +282,23 @@ class DemoPage extends StatelessWidget { } ``` -1. Run the app. When you click the 'Show Flutter homepage' you should see the - phone's default browser open, and the Flutter homepage appear. +1. Run the app. Click **Show Flutter homepage**. You should see the + default browser open on the device, displaying the Flutter homepage. ### Example: Using the URL Launcher package to launch the browser {#url-example} The [URL Launcher]({{site.pub-pkg}}/url_launcher) plugin package enables you to open the default browser on the mobile platform to -display a given URL. It demonstrates how packages may also contain -platform-specific code (we call these packages 'plugins'). -It is supported on both Android and iOS. +display a given URL, and is supported on both Android and iOS. +It demonstrates how packages may also contain +platform-specific code—these packages are often called _plugins_. To use this plugin: 1. Create a new project called 'launchdemo' -1. Open `pubspec.yaml`, and replace: - ``` - dependencies: - flutter: - sdk: flutter - ``` - with: +1. Open `pubspec.yaml`, and add the `url_launcher` dependency: ``` dependencies: @@ -308,7 +307,7 @@ To use this plugin: url_launcher: ^0.4.1 ``` -1. Run `flutter pub get` in the terminal, or click 'Packages get' in +1. Run `flutter pub get` in the terminal, or click **Packages get** in IntelliJ 1. Open `lib/main.dart` and replace its full contents with: @@ -332,7 +331,7 @@ class MyApp extends StatelessWidget { class DemoPage extends StatelessWidget { launchURL() { - launch('https://flutter.io'); + launch('https://flutter.dev'); } @override @@ -349,7 +348,8 @@ class DemoPage extends StatelessWidget { } ``` -1. Run the app (or stop and restart it, if you already had it running - before adding the plugin). When you click the 'Show Flutter homepage' - you should see the phone's default browser open, and the Flutter - homepage appear. \ No newline at end of file +1. Run the app (or stop and restart it, if it was already running + before adding the plugin). Click **Show Flutter homepage**. + You should see the default browser open on the device, + displaying the Flutter homepage. + From e9a5cef8ee44031d0eafece90b04094a93b8abae Mon Sep 17 00:00:00 2001 From: Shams Zakhour Date: Wed, 29 May 2019 10:13:11 -0700 Subject: [PATCH 2/6] Fixing a bug. --- src/docs/development/packages-and-plugins/using-packages.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/docs/development/packages-and-plugins/using-packages.md b/src/docs/development/packages-and-plugins/using-packages.md index ebf07a1fb92..766c710294a 100644 --- a/src/docs/development/packages-and-plugins/using-packages.md +++ b/src/docs/development/packages-and-plugins/using-packages.md @@ -282,8 +282,7 @@ class DemoPage extends StatelessWidget { } ``` -1. Run the app. Click **Show Flutter homepage**. You should see the - default browser open on the device, displaying the Flutter homepage. +1. Run the app. The app's background should now be orange. ### Example: Using the URL Launcher package to launch the browser {#url-example} @@ -300,7 +299,7 @@ To use this plugin: 1. Open `pubspec.yaml`, and add the `url_launcher` dependency: - ``` + ```yaml dependencies: flutter: sdk: flutter From 303daa1b41abbea71f2672a7105c48e1725221aa Mon Sep 17 00:00:00 2001 From: Shams Zakhour Date: Wed, 29 May 2019 20:19:19 -0700 Subject: [PATCH 3/6] Returning to the previous version range for the sdk --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index d240a9db0af..92756621b93 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -440,4 +440,4 @@ packages: source: hosted version: "2.1.15" sdks: - dart: ">=2.2.0 <3.0.0" + dart: ">=2.3.0-dev.0.1 <3.0.0" From eb50d252ce2fe126334c84427a753cf0cd1be282 Mon Sep 17 00:00:00 2001 From: Shams Zakhour Date: Mon, 3 Jun 2019 16:15:15 -0700 Subject: [PATCH 4/6] Text changes to follow our dev guidelines. --- src/docs/codelabs/layout-basics.md | 12 +- .../cookbook/animation/animated-container.md | 37 ++-- .../cookbook/animation/opacity-animation.md | 95 ++++---- src/docs/cookbook/design/drawer.md | 72 +++--- src/docs/cookbook/design/fonts.md | 73 +++--- src/docs/cookbook/design/orientation.md | 55 ++--- src/docs/cookbook/design/package-fonts.md | 19 +- src/docs/cookbook/design/snackbars.md | 62 +++--- src/docs/cookbook/design/tabs.md | 51 +++-- src/docs/cookbook/design/themes.md | 66 +++--- src/docs/cookbook/forms/focus.md | 94 ++++---- src/docs/cookbook/forms/retrieve-input.md | 86 +++---- src/docs/cookbook/forms/text-field-changes.md | 91 ++++---- src/docs/cookbook/forms/text-input.md | 20 +- src/docs/cookbook/forms/validation.md | 138 ++++++------ src/docs/cookbook/gestures/dismissible.md | 95 ++++---- src/docs/cookbook/gestures/handling-taps.md | 46 ++-- src/docs/cookbook/gestures/ripples.md | 26 ++- src/docs/cookbook/images/cached-images.md | 10 +- src/docs/cookbook/images/fading-in-images.md | 19 +- src/docs/cookbook/images/network-image.md | 20 +- src/docs/cookbook/lists/basic-list.md | 10 +- src/docs/cookbook/lists/floating-app-bar.md | 97 ++++---- src/docs/cookbook/lists/grid-lists.md | 26 ++- src/docs/cookbook/lists/horizontal-list.md | 12 +- src/docs/cookbook/lists/long-lists.md | 24 +- src/docs/cookbook/lists/mixed-list.md | 71 +++--- .../cookbook/maintenance/error-reporting.md | 24 +- .../cookbook/navigation/hero-animations.md | 61 ++--- src/docs/cookbook/navigation/named-routes.md | 81 +++---- .../navigation/navigate-with-arguments.md | 50 +++-- .../cookbook/navigation/navigation-basics.md | 18 +- src/docs/cookbook/navigation/passing-data.md | 89 ++++---- .../cookbook/navigation/returning-data.md | 74 +++---- .../networking/authenticated-requests.md | 6 +- .../cookbook/networking/background-parsing.md | 57 ++--- src/docs/cookbook/networking/fetch-data.md | 56 ++--- src/docs/cookbook/networking/web-sockets.md | 24 +- src/docs/cookbook/persistence/key-value.md | 33 +-- .../persistence/reading-writing-files.md | 54 ++--- src/docs/cookbook/persistence/sqlite.md | 209 +++++++++--------- .../cookbook/plugins/picture-using-camera.md | 124 +++++------ src/docs/cookbook/plugins/play-video.md | 93 ++++---- .../testing/integration/introduction.md | 122 +++++----- .../cookbook/testing/integration/profiling.md | 117 +++++----- .../cookbook/testing/integration/scrolling.md | 137 ++++++------ .../cookbook/testing/unit/introduction.md | 18 +- src/docs/cookbook/testing/unit/mocking.md | 59 ++--- src/docs/cookbook/testing/widget/finders.md | 106 ++++----- .../cookbook/testing/widget/introduction.md | 179 +++++++-------- src/docs/cookbook/testing/widget/tap-drag.md | 139 ++++++------ src/docs/deployment/android.md | 2 +- src/docs/deployment/flavors.md | 10 +- src/docs/development/data-and-backend/json.md | 2 +- .../state-mgmt/declarative.md | 4 +- .../androidx-compatibility.md | 4 +- .../developing-packages.md | 2 +- .../packages-and-plugins/using-packages.md | 71 +++--- .../platform-integration/platform-channels.md | 2 +- src/docs/development/tools/android-studio.md | 6 +- src/docs/development/tools/formatting.md | 8 +- src/docs/development/tools/inspector.md | 4 +- src/docs/development/tools/vs-code.md | 22 +- .../ui/animations/hero-animations.md | 4 +- src/docs/development/ui/animations/index.md | 24 +- .../development/ui/animations/tutorial.md | 4 +- src/docs/development/ui/assets-and-images.md | 2 +- src/docs/development/ui/interactive.md | 2 +- .../development/ui/layout/box-constraints.md | 4 +- src/docs/development/ui/layout/index.md | 2 +- src/docs/get-started/codelab.md | 6 +- .../get-started/flutter-for/android-devs.md | 6 +- .../get-started/flutter-for/declarative.md | 6 +- src/docs/get-started/flutter-for/ios-devs.md | 2 +- .../flutter-for/react-native-devs.md | 6 +- .../flutter-for/xamarin-forms-devs.md | 18 +- src/docs/get-started/install/_get-sdk-win.md | 7 +- src/docs/get-started/install/_get-sdk.md | 4 +- src/docs/get-started/install/_ios-setup.md | 13 +- src/docs/get-started/install/chromeos.md | 2 +- src/docs/index.md | 2 +- src/docs/resources/bootstrap-into-dart.md | 2 +- src/docs/resources/bug-reports.md | 15 +- src/docs/resources/faq.md | 64 +++--- src/docs/resources/inside-flutter.md | 2 +- src/docs/resources/videos.md | 2 +- src/docs/testing/best-practices.md | 22 +- src/docs/testing/debugging.md | 2 +- src/docs/testing/index.md | 2 +- src/docs/testing/oem-debuggers.md | 4 +- src/docs/testing/ui-performance/index.md | 10 +- 91 files changed, 1927 insertions(+), 1806 deletions(-) diff --git a/src/docs/codelabs/layout-basics.md b/src/docs/codelabs/layout-basics.md index 931c7f99cb4..4a459e39902 100644 --- a/src/docs/codelabs/layout-basics.md +++ b/src/docs/codelabs/layout-basics.md @@ -6,18 +6,18 @@ toc: false {{site.alert.note}} This codelab is being used to test out some new features of - DartPad! You may encounter bugs, + DartPad! You might encounter bugs, malapropisms, annoyances, and other general weirdness. - If that happens, please take a moment to + If that happens, we appreciate if you take a moment to [file a bug on GitHub](https://github.com/dart-lang/dart-pad/issues/new). Feature requests and suggestions are also greatly appreciated. {{site.alert.end}} {{site.alert.note}} This codelab is currently being developed and tested - with Chrome. There may be (in the short term) features that - work in some browsers and not others. If you encounter - any, please feel free to + with Chrome. There might be (in the short term) features that + work in some browsers and not others. If you encounter any, + feel free to [file a bug on GitHub](https://goo.gle/flutter_web_issue), labelling the issue with `platform-web`. {{site.alert.end}} @@ -75,7 +75,7 @@ Here's the example you just finished. Try setting the `Row`'s If you've set the `mainAxisSize` of a `Row` to the minimum, there won't be any extra room beyond what the children use. -If you've set it to `max`, though, the `Row` may have some +If you've set it to `max`, though, the `Row` might have some additional space lying around. You can use the `mainAxisAlignment` property to control how the `Row` aligns its children within that space. diff --git a/src/docs/cookbook/animation/animated-container.md b/src/docs/cookbook/animation/animated-container.md index 3e3041a3654..03c0959b0a4 100644 --- a/src/docs/cookbook/animation/animated-container.md +++ b/src/docs/cookbook/animation/animated-container.md @@ -1,7 +1,7 @@ --- title: Animate the properties of a Container next: - title: Fade a Widget in and out + title: Fade a widget in and out path: /docs/cookbook/animation/opacity-animation --- @@ -10,12 +10,13 @@ class provides a convenient way to create a widget with specific properties: width, height, background color, padding, borders, and more. Simple animations often involve changing these properties over time. -For example, you may want to animate the background color from grey to green to +For example, +you might want to animate the background color from grey to green to indicate that an item has been selected by the user. To animate these properties, Flutter provides the [`AnimatedContainer`]({{site.api}}/flutter/widgets/AnimatedContainer-class.html) -widget. Like the `Container` Widget, `AnimatedContainer` allows you to define +widget. Like the `Container` widget, `AnimatedContainer` allows you to define the width, height, background colors, and more. However, when the `AnimatedContainer` is rebuilt with new properties, it automatically animates between the old and new values. In Flutter, these types of @@ -35,12 +36,12 @@ background color, and border radius when the user taps a button. To start, create [`StatefulWidget`]({{site.api}}/flutter/widgets/StatefulWidget-class.html) and [`State`]({{site.api}}/flutter/widgets/State-class.html) classes. -Use the custom State class to define the properties you need to change over +Use the custom State class to define the properties that change over time. In this example, that includes the width, height, color, and border -radius. In addition, you can also define the default value of each property. +radius. In addition, also define the default value of each property. -These properties must belong to a custom `State` class so they can be updated -when the user taps a button. +These properties belong to a custom `State` class so they +can be updated when the user taps a button. ```dart @@ -59,15 +60,15 @@ class _AnimatedContainerAppState extends State { @override Widget build(BuildContext context) { - // Fill this out in the next steps + // Fill this out in the next steps. } } ``` ## 2. Build an `AnimatedContainer` using the properties -Next, you can build the `AnimatedContainer` using the properties defined in the -previous step. Furthermore, you must provide a `duration` that defines how long +Next, build the `AnimatedContainer` using the properties defined in the +previous step. Furthermore, provide a `duration` that defines how long the animation should run. @@ -90,17 +91,17 @@ AnimatedContainer( ## 3. Start the animation by rebuilding with new properties Finally, start the animation by rebuilding the `AnimatedContainer` with -new properties. How to trigger a rebuild? When it comes to `StatefulWidgets`, -[`setState`]({{site.api}}/flutter/widgets/State/setState.html) is the -solution. +the new properties. How to trigger a rebuild? Use the +[`setState()`]({{site.api}}/flutter/widgets/State/setState.html) +method. -For this example, add a button to the app. When the user taps the button, update +Add a button to the app. When the user taps the button, update the properties with a new width, height, background color and border radius -inside a call to `setState`. +inside a call to `setState()`. -In a real app, you most often transition between fixed values (for example, from -a grey to a green background). For this app, generate new values each time the -user taps the button. +A real app typically transitions between fixed values (for example, +from a grey to a green background). For this app, +generate new values each time the user taps the button. ```dart diff --git a/src/docs/cookbook/animation/opacity-animation.md b/src/docs/cookbook/animation/opacity-animation.md index ab6afd389fb..c48e77d46a2 100644 --- a/src/docs/cookbook/animation/opacity-animation.md +++ b/src/docs/cookbook/animation/opacity-animation.md @@ -1,5 +1,5 @@ --- -title: Fade a Widget in and out +title: Fade a widget in and out prev: title: Animate the properties of a Container path: /docs/cookbook/animation/animated-container @@ -8,24 +8,24 @@ next: path: /docs/cookbook/design/drawer --- -As UI developers, you often need to show and hide elements on screen. However, +UI developers often need to show and hide elements on screen. However, quickly popping elements on and off the screen can feel jarring to end users. -Instead, you can fade elements in and out with an opacity animation to create +Instead, fade elements in and out with an opacity animation to create a smooth experience. -In Flutter, you can achieve this task using the [`AnimatedOpacity`][] Widget. +In Flutter, achieve by using the [`AnimatedOpacity`][] widget. ## Directions - 1. Show a box to fade in and out + 1. Create a box to fade in and out 2. Define a `StatefulWidget` 3. Display a button that toggles the visibility 4. Fade the box in and out ## 1. Create a box to fade in and out -First, you'll need something to fade in and out. In this example, -you'll draw a green box on screen. +First, create something to fade in and out. For this example, +draw a green box on screen. ```dart @@ -38,26 +38,27 @@ Container( ## 2. Define a `StatefulWidget` -Now that you have a green box to animate, you'll need a way to know whether the -box should be visible or invisible. To accomplish this, use a -[`StatefulWidget`][]. +Now that you have a green box to animate, +you need a way to know whether the box should be visible. +To accomplish this, use a [`StatefulWidget`][]. -A `StatefulWidget` is a class that creates a `State` object. The `State` object -holds some data about our app and provides a way to update that data. When you -update the data, you can also ask Flutter to rebuild our UI with those changes. +A `StatefulWidget` is a class that creates a `State` object. +The `State` object holds some data about the app and provides a way to +update that data. When updating the data, +you can also ask Flutter to rebuild the UI with those changes. -In this case, you'll have one piece of data: a boolean representing whether the -button is visible or invisible. +In this case, you have one piece of data: +a boolean representing whether the button is visible. -To construct a `StatefulWidget`, you need to create two classes: A -`StatefulWidget` and a corresponding `State` class. Pro tip: The Flutter plugins -for Android Studio and VSCode include the `stful` snippet to quickly generate -this code. +To construct a `StatefulWidget`, create two classes: A +`StatefulWidget` and a corresponding `State` class. +Pro tip: The Flutter plugins for Android Studio and VSCode include +the `stful` snippet to quickly generate this code. ```dart -// The StatefulWidget's job is to take in some data and create a State class. -// In this case, our Widget takes in a title, and creates a _MyHomePageState. +// The StatefulWidget's job is to take data and create a State class. +// In this case, the widget takes a title, and creates a _MyHomePageState. class MyHomePage extends StatefulWidget { final String title; @@ -70,7 +71,7 @@ class MyHomePage extends StatefulWidget { // The State class is responsible for two things: holding some data you can // update and building the UI using that data. class _MyHomePageState extends State { - // Whether the green box should be visible or invisible + // Whether the green box should be visible. bool _visible = true; @override @@ -82,23 +83,25 @@ class _MyHomePageState extends State { ## 3. Display a button that toggles the visibility -Now that you have some data to determine whether the green box should be visible -or invisible, you'll need a way update that data. In this case, if the box is -visible, you want to hide it. If the box is hidden, you want to show it. +Now that you have some data to determine whether the green box +should be visible, you need a way update that data. +In this example, if the box is visible, hide it. +If the box is hidden, show it. -To achieve this, you'll display a button. When a user presses the button, you'll -flip the boolean from true to false, or false to true. You need to make this -change using [`setState`][], which is a method on the `State` class. -This lets Flutter know it needs to rebuild the Widget. +To achieve this, display a button. When a user presses the button, +flip the boolean from true to false, or false to true. +Make this change using [`setState()`][], +which is a method on the `State` class. +This tells Flutter to rebuild the widget. -Note: For more information on working with user input, please see the +For more information on working with user input, see the [Gestures](/docs/cookbook#gestures) section of the Cookbook. ```dart FloatingActionButton( onPressed: () { - // Make sure to call setState. This tells Flutter to rebuild the + // Call setState. This tells Flutter to rebuild the // UI with the changes. setState(() { _visible = !_visible; @@ -112,23 +115,23 @@ FloatingActionButton( ## 4. Fade the box in and out You've got a green box on screen. You've got a button to toggle the visibility -to `true` or `false`. So how do you fade the box in and out? With an -[`AnimatedOpacity`][] Widget. +to `true` or `false`. So how to fade the box in and out? With an +[`AnimatedOpacity`][] widget. -The `AnimatedOpacity` Widget requires three arguments: +The `AnimatedOpacity` widget requires three arguments: * `opacity`: A value from 0.0 (invisible) to 1.0 (fully visible). * `duration`: How long the animation should take to complete. - * `child`: The Widget to animate. In our case, the green box. + * `child`: The widget to animate. In this case, the green box. ```dart AnimatedOpacity( - // If the Widget should be visible, animate to 1.0 (fully visible). If - // the Widget should be hidden, animate to 0.0 (invisible). + // If the widget is visible, animate to 0.0 (invisible). + // If the widget is hidden, animate to 1.0 (fully visible). opacity: _visible ? 1.0 : 0.0, duration: Duration(milliseconds: 500), - // The green box needs to be the child of the AnimatedOpacity + // The green box must be a child of the AnimatedOpacity widget. child: Container( width: 200.0, height: 200.0, @@ -155,8 +158,8 @@ class MyApp extends StatelessWidget { } } -// The StatefulWidget's job is to take in some data and create a State class. -// In this case, the Widget takes a title, and creates a _MyHomePageState. +// The StatefulWidget's job is to take data and create a State class. +// In this case, the widget takes a title, and creates a _MyHomePageState. class MyHomePage extends StatefulWidget { final String title; @@ -169,7 +172,7 @@ class MyHomePage extends StatefulWidget { // The State class is responsible for two things: holding some data you can // update and building the UI using that data. class _MyHomePageState extends State { - // Whether the green box should be visible or invisible + // Whether the green box should be visible bool _visible = true; @override @@ -180,11 +183,11 @@ class _MyHomePageState extends State { ), body: Center( child: AnimatedOpacity( - // If the Widget should be visible, animate to 1.0 (fully visible). - // If the Widget should be hidden, animate to 0.0 (invisible). + // If the widget is visible, animate to 0.0 (invisible). + // If the widget is hidden, animate to 1.0 (fully visible). opacity: _visible ? 1.0 : 0.0, duration: Duration(milliseconds: 500), - // The green box needs to be the child of the AnimatedOpacity + // The green box must be a child of the AnimatedOpacity widget. child: Container( width: 200.0, height: 200.0, @@ -194,7 +197,7 @@ class _MyHomePageState extends State { ), floatingActionButton: FloatingActionButton( onPressed: () { - // Make sure to call setState. This tells Flutter to rebuild the + // Call setState. This tells Flutter to rebuild the // UI with the changes. setState(() { _visible = !_visible; @@ -212,4 +215,4 @@ class _MyHomePageState extends State { [`AnimatedOpacity`]: {{site.api}}/flutter/widgets/AnimatedOpacity-class.html [`StatefulWidget`]: {{site.api}}/flutter/widgets/StatefulWidget-class.html -[`setState`]: {{site.api}}/flutter/widgets/State/setState.html +[`setState()`]: {{site.api}}/flutter/widgets/State/setState.html diff --git a/src/docs/cookbook/design/drawer.md b/src/docs/cookbook/design/drawer.md index e3966a70826..7d74bb0cad0 100644 --- a/src/docs/cookbook/design/drawer.md +++ b/src/docs/cookbook/design/drawer.md @@ -1,20 +1,23 @@ --- title: Add a Drawer to a screen prev: - title: Fade a Widget in and out + title: Fade a widget in and out path: /docs/cookbook/animation/opacity-animation next: title: Displaying SnackBars path: /docs/cookbook/design/snackbars --- -In apps that employ Material Design, there are two primary options for -navigation: tabs and drawers. When there is insufficient space to support tabs, -Drawers provide a handy alternative. +In apps that use Material Design, +there are two primary options for navigation: tabs and drawers. +When there is insufficient space to support tabs, +drawers provide a handy alternative. -In Flutter, we can use the [`Drawer`]({{site.api}}/flutter/material/Drawer-class.html) -Widget in combination with a [`Scaffold`]({{site.api}}/flutter/material/Scaffold-class.html) -to create a layout with a Material Design Drawer. +In Flutter, use the +[`Drawer`]({{site.api}}/flutter/material/Drawer-class.html) +widget in combination with a +[`Scaffold`]({{site.api}}/flutter/material/Scaffold-class.html) +to create a layout with a Material Design drawer. ## Directions @@ -25,25 +28,26 @@ to create a layout with a Material Design Drawer. ## 1. Create a `Scaffold` -In order to add a Drawer to our app, we'll need to wrap it in a -[Scaffold]({{site.api}}/flutter/material/Scaffold-class.html) -Widget. The Scaffold Widget provides a consistent visual structure to apps that -follow the Material Design Guidelines. It also supports special Material Design +To add a drawer to the app, wrap it in a +[Scaffold]({{site.api}}/flutter/material/Scaffold-class.html) widget. +The Scaffold widget provides a consistent visual structure to apps that +follow the Material Design Guidelines. +It also supports special Material Design components, such as Drawers, AppBars, and SnackBars. -In this case, we'll want to create a `Scaffold` with a `drawer`: +In this example, create a `Scaffold` with a `drawer`: ```dart Scaffold( - drawer: // We'll add our Drawer here in the next step! + drawer: // Add a Drawer here in the next step. ); ``` ## 2. Add a drawer -We can now add a drawer to our `Scaffold`. A drawer could be any Widget, but -it's often best to use the `Drawer` widget from the +Now add a drawer to the `Scaffold`. A drawer can be any widget, +but it's often best to use the `Drawer` widget from the [material library]({{site.api}}/flutter/material/material-library.html), which adheres to the Material Design spec. @@ -51,30 +55,31 @@ which adheres to the Material Design spec. ```dart Scaffold( drawer: Drawer( - child: // We'll populate the Drawer in the next step! + child: // Populate the Drawer in the next step. ) ); ``` ## 3. Populate the drawer with items -Now that we have a `Drawer` in place, we can add content to it. In this example, -we will use a [`ListView`]({{site.api}}/flutter/widgets/ListView-class.html). -While we could use a `Column` Widget, `ListView` is handy in this situation -because it will allow users to scroll through the drawer if the content takes up -more space than the screen supports. +Now that you have a `Drawer` in place, add content to it. +For this example, +use a [`ListView`]({{site.api}}/flutter/widgets/ListView-class.html). +While you could use a `Column` widget, `ListView` is handy +because it allows users to scroll through the drawer if the +content takes more space than the screen supports. -We will populate the `ListView` with a +Populate the `ListView` with a [`DrawerHeader`]({{site.api}}/flutter/material/DrawerHeader-class.html) and two [`ListTile`]({{site.api}}/flutter/material/ListTile-class.html) -Widgets. For more information on working with Lists, please see the +widgets. For more information on working with Lists, see the [list recipes](/docs/cookbook#lists). ```dart Drawer( // Add a ListView to the drawer. This ensures the user can scroll - // through the options in the Drawer if there isn't enough vertical + // through the options in the drawer if there isn't enough vertical // space to fit everything. child: ListView( // Important: Remove any padding from the ListView. @@ -89,14 +94,14 @@ Drawer( ListTile( title: Text('Item 1'), onTap: () { - // Update the state of the app + // Update the state of the app. // ... }, ), ListTile( title: Text('Item 2'), onTap: () { - // Update the state of the app + // Update the state of the app. // ... }, ), @@ -107,11 +112,12 @@ Drawer( ## 4. Close the drawer programmatically -After a user taps on an item, we often want to close the drawer. How can we -achieve this? Using the [Navigator]({{site.api}}/flutter/widgets/Navigator-class.html)! +After a user taps an item, you might want to close the drawer. +How to achieve this? By using the +[Navigator]({{site.api}}/flutter/widgets/Navigator-class.html). -When a user opens the Drawer, Flutter adds the drawer to the navigation -stack under the hood. Therefore, to close the drawer, we can call +When a user opens the drawer, Flutter adds the drawer to the navigation +stack behind the scenes. Therefore, to close the drawer, call `Navigator.pop(context)`. @@ -119,9 +125,9 @@ stack under the hood. Therefore, to close the drawer, we can call ListTile( title: Text('Item 1'), onTap: () { - // Update the state of the app + // Update the state of the app. // ... - // Then close the drawer + // Then close the drawer. Navigator.pop(context); }, ), @@ -158,7 +164,7 @@ class MyHomePage extends StatelessWidget { body: Center(child: Text('My Page!')), drawer: Drawer( // Add a ListView to the drawer. This ensures the user can scroll - // through the options in the Drawer if there isn't enough vertical + // through the options in the drawer if there isn't enough vertical // space to fit everything. child: ListView( // Important: Remove any padding from the ListView. diff --git a/src/docs/cookbook/design/fonts.md b/src/docs/cookbook/design/fonts.md index d6538e08f99..e0c88731d91 100644 --- a/src/docs/cookbook/design/fonts.md +++ b/src/docs/cookbook/design/fonts.md @@ -6,33 +6,34 @@ prev: title: Using Themes to share colors and font styles path: /docs/cookbook/design/themes next: - title: Working with Tabs + title: Working with tabs path: /docs/cookbook/design/tabs --- -While Android and iOS offer high quality system fonts, one of the most common -requests from designers is to use custom fonts. For example, you may have a -custom-built font from a designer, or maybe you downloaded a font from +While Android and iOS offer high quality system fonts, +one of the most common requests from designers is for custom fonts. +For example, you might have a custom-built font from a designer, +or perhaps you downloaded a font from [Google Fonts](https://fonts.google.com). -Flutter works out of the box with custom fonts. You can apply fonts across an -entire app or to individual Widgets. +Flutter works out of the box with custom fonts. +You can apply fonts across an entire app or to individual widgets. ## Directions 1. Import the font files - 2. Declare the font in the `pubspec.yaml` + 2. Declare the font in the `pubspec.yaml` file 3. Set a font as the default - 4. Use a font in a specific Widget + 4. Use a font in a specific widget ## 1. Import the font files -In order to work with a font, you need to import the font files into the -project. It is common practice to put font files in a `fonts` or `assets` +To work with a font, import the font files into the project. +It's common practice to put font files in a `fonts` or `assets` folder at the root of a Flutter project. -For example, if you want to import the Raleway and Roboto Mono font files into -a project, the folder structure would look like this: +For example, to import the Raleway and Roboto Mono font +files into a project, the folder structure might look like this: ``` awesome_app/ @@ -43,10 +44,10 @@ awesome_app/ RobotoMono-Bold.ttf ``` -## 2. Declare the font in the `pubspec.yaml` +## 2. Declare the font in the `pubspec.yaml` file -Now that you have a font to work with, you need to tell Flutter where to -find it. You can do so by including a font definition in the `pubspec.yaml`. +Once you've identified a font, tell Flutter where to find it. +You can do this by including a font definition in the `pubspec.yaml`. ```yaml flutter: @@ -67,18 +68,20 @@ flutter: The `family` determines the name of the font, which you use in the [`fontFamily`]({{site.api}}/flutter/painting/TextStyle/fontFamily.html) -property of a [`TextStyle`]({{site.api}}/flutter/painting/TextStyle-class.html) +property of a +[`TextStyle`]({{site.api}}/flutter/painting/TextStyle-class.html) object. The `asset` is a path to the font file, relative to the `pubspec.yaml` file. -These files contain the outlines for the glyphs in the font. When building the -app, these files are included in the app's asset bundle. +These files contain the outlines for the glyphs in the font. +When building the app, these files are included in the app's asset bundle. -A single font can reference many different files with different outline weights -and styles: +A single font can reference many different files with different +outline weights and styles: - * The `weight` property specifies the weight of the outlines in the file as an - integer multiple of 100, between 100 and 900. These values correspond to the + * The `weight` property specifies the weight of the outlines in + the file as an integer multiple of 100, between 100 and 900. + These values correspond to the [`FontWeight`]({{site.api}}/flutter/dart-ui/FontWeight-class.html) and can be used in the [`fontWeight`]({{site.api}}/flutter/painting/TextStyle/fontWeight.html) @@ -96,7 +99,7 @@ and styles: ## 3. Set a font as the default You have two options for how to apply fonts to text: as the default font -or only within specific Widgets. +or only within specific widgets. To use a font as the default, set the `fontFamily` property as part of the app's `theme`. The value provided to `fontFamily` must match the `family` @@ -106,22 +109,22 @@ name declared in the `pubspec.yaml`. ```dart MaterialApp( title: 'Custom Fonts', - // Set Raleway as the default app font + // Set Raleway as the default app font. theme: ThemeData(fontFamily: 'Raleway'), home: MyHomePage(), ); ``` -For more information on themes, please view the ["Using Themes to share colors -and font styles"](/docs/cookbook/design/themes) recipe. +For more information on themes, see the [Using Themes to share colors +and font styles](/docs/cookbook/design/themes) recipe. -## 4. Use the font in a specific Widget +## 4. Use the font in a specific widget -If you want to apply the font to a specific Widget, such as a `Text` Widget, +If you want to apply the font to a specific widget, such as a `Text` widget, provide a [`TextStyle`]({{site.api}}/flutter/painting/TextStyle-class.html) -to the Widget. +to the widget. -In this example, you'll apply the RobotoMono font to a single `Text` Widget. +In this example, apply the RobotoMono font to a single `Text` widget. Once again, the `fontFamily` must match the `family` name declared in the `pubspec.yaml`. @@ -136,8 +139,8 @@ Text( ### TextStyle If a [`TextStyle`]({{site.api}}/flutter/painting/TextStyle-class.html) -object specifies a weight or style for which is there is no exact font file, the -engine uses one of the more generic files for the font and attempts to +object specifies a weight or style for which is there is no exact font file, +the engine uses one of the more generic files for the font and attempts to extrapolate outlines for the requested weight and style. ## Complete example @@ -188,7 +191,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Custom Fonts', - // Set Raleway as the default app font + // Set Raleway as the default app font. theme: ThemeData(fontFamily: 'Raleway'), home: MyHomePage(), ); @@ -199,10 +202,10 @@ class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - // The AppBar uses the app-default Raleway font + // The AppBar uses the app-default Raleway font. appBar: AppBar(title: Text('Custom Fonts')), body: Center( - // This Text Widget uses the RobotoMono font + // This Text widget uses the RobotoMono font. child: Text( 'Roboto Mono sample', style: TextStyle(fontFamily: 'RobotoMono'), diff --git a/src/docs/cookbook/design/orientation.md b/src/docs/cookbook/design/orientation.md index 743aedcd287..170010ceef7 100644 --- a/src/docs/cookbook/design/orientation.md +++ b/src/docs/cookbook/design/orientation.md @@ -8,26 +8,28 @@ next: path: /docs/cookbook/design/themes --- -In certain cases, it can be handy to update the design of an app when the user -rotates their screen from portrait mode to landscape mode. For example, we may -want to show one item after the next in portrait mode, yet put those same items -side-by-side in landscape mode. +In some situations, +it important to update the display of an app when the user +rotates their screen from portrait mode to landscape mode. For example, +the app might show one item after the next in portrait mode, +yet put those same items side-by-side in landscape mode. -In Flutter, we can build different layouts depending on a given +In Flutter, build different layouts depending on a given [`Orientation`]({{site.api}}/flutter/widgets/Orientation-class.html). -In this example, we'll build a list that displays 2 columns in portrait mode and -3 columns in landscape mode. +In this example, build a list that displays two columns in +portrait mode and three columns in landscape mode. ## Directions - 1. Build a `GridView` with 2 columns + 1. Build a `GridView` with two columns 2. Use an `OrientationBuilder` to change the number of columns -## 1. Build a `GridView` with 2 columns +## 1. Build a `GridView` with two columns -First, we'll need a list of items to work with. Rather than using a normal list, -we'll want a list that displays items in a Grid. For now, we'll create a grid -with 2 columns. +First, create a list of items to work with. +Rather than using a normal list, +create a list that displays items in a grid. +For now, create a grid with two columns. ```dart @@ -38,36 +40,39 @@ GridView.count( ); ``` -To learn more about working with `GridViews`, please see the +To learn more about working with `GridViews`, see the [Creating a grid list](/docs/cookbook/lists/grid-lists/) recipe. ## 2. Use an `OrientationBuilder` to change the number of columns -In order to determine the current `Orientation`, we can use the +To determine the app's current `Orientation`, use the [`OrientationBuilder`]({{site.api}}/flutter/widgets/OrientationBuilder-class.html) -Widget. The `OrientationBuilder` calculates the current `Orientation` by -comparing the width and height available to the parent widget, and rebuilds -when the size of the parent changes. +widget. The `OrientationBuilder` calculates the current `Orientation` by +comparing the width and height available to the parent widget, +and rebuilds when the size of the parent changes. -Using the `Orientation`, we can build a list that displays 2 columns in portrait -mode, or 3 columns in landscape mode. +Using the `Orientation`, build a list that displays two columns in portrait +mode, or three columns in landscape mode. ```dart OrientationBuilder( builder: (context, orientation) { return GridView.count( - // Create a grid with 2 columns in portrait mode, or 3 columns in - // landscape mode. + // Create a grid with 2 columns in portrait mode, + // or 3 columns in landscape mode. crossAxisCount: orientation == Orientation.portrait ? 2 : 3, ); }, ); ``` -Note: If you're interested in the orientation of the screen, rather than -the amount of space available to the parent, please use -`MediaQuery.of(context).orientation` instead of an `OrientationBuilder` Widget. +{{site.alert.note}} + If you're interested in the orientation of the screen, + rather than the amount of space available to the parent, + use `MediaQuery.of(context).orientation` instead of an + `OrientationBuilder` widget. +{{site.alert.end}} ## Complete example @@ -107,7 +112,7 @@ class OrientationList extends StatelessWidget { // Create a grid with 2 columns in portrait mode, or 3 columns in // landscape mode. crossAxisCount: orientation == Orientation.portrait ? 2 : 3, - // Generate 100 Widgets that display their index in the List + // Generate 100 widgets that display their index in the List. children: List.generate(100, (index) { return Center( child: Text( diff --git a/src/docs/cookbook/design/package-fonts.md b/src/docs/cookbook/design/package-fonts.md index 99410e2cd44..b9b1c7aa46f 100644 --- a/src/docs/cookbook/design/package-fonts.md +++ b/src/docs/cookbook/design/package-fonts.md @@ -39,8 +39,8 @@ awesome_package/ ## 2. Add the package and fonts to the app -You can now consume the package and use the fonts it provides. -This involves updating the `pubspec.yaml` in the *app's* root directory. +Adding the fonts to a package +involves updating the `pubspec.yaml` in the *app's* root directory. ### Add the package to the project @@ -51,11 +51,12 @@ dependencies: ### Declare the font assets -Now that you've imported the package, you need to tell Flutter where to +Now that you've imported the package, tell Flutter where to find the fonts from the `awesome_package`. -To declare package fonts, you must must prefix the path to the font with -`packages/awesome_package`. This tells Flutter to look in the `lib` folder +To declare package fonts, prefix the path to the font with +`packages/awesome_package`. +This tells Flutter to look in the `lib` folder of the package for the font. ```yaml @@ -70,9 +71,9 @@ flutter: ## 3. Use the font -You can use a [`TextStyle`][] to change the appearance of text. -To use package fonts, you need to not only declare which font you'd like to use, -you need to declare the `package` the font belongs to. +Use a [`TextStyle`][] to change the appearance of text. +To use package fonts, declare which font you'd like to use and +which the `package` the font belongs to. ```dart @@ -141,7 +142,7 @@ class MyHomePage extends StatelessWidget { // The AppBar uses the app-default Raleway font. appBar: AppBar(title: Text('Package Fonts')), body: Center( - // This Text Widget uses the RobotoMono font. + // This Text widget uses the RobotoMono font. child: Text( 'Using the Raleway font from the awesome_package', style: TextStyle( diff --git a/src/docs/cookbook/design/snackbars.md b/src/docs/cookbook/design/snackbars.md index 8f32441eb85..e5175af05b2 100644 --- a/src/docs/cookbook/design/snackbars.md +++ b/src/docs/cookbook/design/snackbars.md @@ -10,10 +10,10 @@ next: path: /docs/cookbook/design/package-fonts --- -In some cases, it can be handy to briefly inform our users when certain actions -take place. For example, when a user swipes away a message in a list, we might -want to inform them the message has been deleted. We might even want to give -them an option to undo the action! +It can be useful to briefly inform your users when certain actions +take place. For example, when a user swipes away a message in a list, +you might want to inform them that the message has been deleted. +You might even want to give them an option to undo the action. In Material Design, this is the job of a [SnackBar]({{site.api}}/flutter/material/SnackBar-class.html). @@ -22,20 +22,21 @@ In Material Design, this is the job of a 1. Create a `Scaffold` 2. Display a `SnackBar` - 3. Provide an additional action + 3. Provide an optional action ## 1. Create a `Scaffold` -When creating apps that follow the Material Design guidelines, we'll want to -give our apps a consistent visual structure. In this case, we'll need to display -the `SnackBar` at the bottom of the screen, without overlapping other important -Widgets, such as the `FloatingActionButton`! +When creating apps that follow the Material Design guidelines, +give your apps a consistent visual structure. +In this example, display the `SnackBar` at the bottom of the screen, +without overlapping other important +widgets, such as the `FloatingActionButton`. The [Scaffold]({{site.api}}/flutter/material/Scaffold-class.html) -Widget from the -[material library]({{site.api}}/flutter/material/material-library.html) -creates this visual structure for us and ensures important Widgets don't -overlap! +widget, from the +[material library]({{site.api}}/flutter/material/material-library.html), +creates this visual structure and ensures that important +widgets don't overlap. ```dart @@ -43,30 +44,32 @@ Scaffold( appBar: AppBar( title: Text('SnackBar Demo'), ), - body: SnackBarPage(), // You'll fill this in below! + body: SnackBarPage(), // Complete this code in the next step. ); ``` ## 2. Display a `SnackBar` -With the `Scaffold` in place, you can display a `SnackBar`! First, you need to -create a `SnackBar`, then display it using the `Scaffold`. +With the `Scaffold` in place, display a `SnackBar`. +First, create a `SnackBar`, then display it using the `Scaffold`. ```dart final snackBar = SnackBar(content: Text('Yay! A SnackBar!')); -// Find the Scaffold in the Widget tree and use it to show a SnackBar +// Find the Scaffold in the widget tree and use it to show a SnackBar. Scaffold.of(context).showSnackBar(snackBar); ``` -## 3. Provide an additional action +## 3. Provide an optional action -In some cases, you might want to provide an additional action to the user when -the SnackBar is displayed. For example, if they've accidentally deleted a -message, we could provide an action to undo that change. +You might want to provide an action to the user when +the SnackBar is displayed. +For example, the user accidentally deletes a message, +but can use an action to undo that change. -To achieve this, we can provide an additional `action` to the `SnackBar` Widget. +To achieve this, +provide an additional `action` to the `SnackBar` widget. ```dart final snackBar = SnackBar( @@ -74,7 +77,7 @@ final snackBar = SnackBar( action: SnackBarAction( label: 'Undo', onPressed: () { - // Some code to undo the change! + // Some code to undo the change. }, ), ); @@ -82,9 +85,11 @@ final snackBar = SnackBar( ## Complete example -Note: In this example, the SnackBar displays when a user taps on a button. For -more information on working with user input, please see the -[Gestures](/docs/cookbook#gestures) section of the Cookbook. +{{site.alert.note}} + In this example, the SnackBar displays when a user taps a button. + For more information on working with user input, see the + [Gestures](/docs/cookbook#gestures) section of the Cookbook. +{{site.alert.end}} ```dart import 'package:flutter/material.dart'; @@ -117,12 +122,13 @@ class SnackBarPage extends StatelessWidget { action: SnackBarAction( label: 'Undo', onPressed: () { - // Some code to undo the change! + // Some code to undo the change. }, ), ); - // Find the Scaffold in the Widget tree and use it to show a SnackBar! + // Find the Scaffold in the widget tree and use + // it to show a SnackBar. Scaffold.of(context).showSnackBar(snackBar); }, child: Text('Show SnackBar'), diff --git a/src/docs/cookbook/design/tabs.md b/src/docs/cookbook/design/tabs.md index d003d8200f0..672d7cf4677 100644 --- a/src/docs/cookbook/design/tabs.md +++ b/src/docs/cookbook/design/tabs.md @@ -1,5 +1,5 @@ --- -title: Working with Tabs +title: Working with tabs prev: title: Using custom fonts path: /docs/cookbook/design/fonts @@ -8,10 +8,17 @@ next: path: /docs/cookbook/forms/validation --- -Working with tabs is a common pattern in apps following the Material Design +Working with tabs is a common pattern in apps that follow the Material Design guidelines. Flutter includes a convenient way to create tab layouts as part of the [material library]({{site.api}}/flutter/material/material-library.html). +{{site.alert.note}} + To create tabs in a Cupertino app, see the + [Building a Cupertino app with + Flutter](https://codelabs.developers.google.com/codelabs/flutter-cupertino/index.html?index=..%2F..index#0) + codelab. +{{site.alert.end}} + ## Directions 1. Create a `TabController` @@ -20,31 +27,33 @@ the [material library]({{site.api}}/flutter/material/material-library.html). ## 1. Create a `TabController` -In order for tabs to work, we'll need to keep the selected tab and content +In order for tabs to work, you need to keep the selected tab and content sections in sync. This is the job of the [`TabController`]({{site.api}}/flutter/material/TabController-class.html). -We can either manually create a `TabController` or use the -[`DefaultTabController`]({{site.api}}/flutter/material/DefaultTabController-class.html) -Widget. Using the `DefaultTabController` is the simplest option, since it will -create a `TabController` for us and make it available to all descendant Widgets. +A TabController can be created manually, or automatically by using a +[DefaultTabController]({{site.api}}/flutter/material/DefaultTabController-class.html) +widget. + +Using `DefaultTabController` is the simplest option, since it +creates a `TabController` and makes it available to all descendant widgets. ```dart DefaultTabController( - // The number of tabs / content sections we need to display + // The number of tabs / content sections to display. length: 3, - child: // See the next step! + child: // Complete this code in the next step. ); ``` ## 2. Create the tabs -Now that we have a `TabController` to work with, we can create our tabs using +Now that you have a `TabController`, create the tabs using the [`TabBar`]({{site.api}}/flutter/material/TabController-class.html) -Widget. In this example, we'll create a `TabBar` with 3 +widget. In this example, create a `TabBar` with three [`Tab`]({{site.api}}/flutter/material/Tab-class.html) -Widgets and place it within an +widgets and place it within an [`AppBar`]({{site.api}}/flutter/material/AppBar-class.html). @@ -65,18 +74,20 @@ DefaultTabController( ); ``` -By default, the `TabBar` looks up the Widget tree for the nearest -`DefaultTabController`. If you're manually creating a `TabController`, you'll -need to pass it to the `TabBar`. +By default, the `TabBar` looks up the widget tree for the nearest +`DefaultTabController`. If you're manually creating a `TabController`, +pass it to the `TabBar`. ## 3. Create content for each tab -Now that we have tabs, we'll want to display content when a tab is selected. -For this purpose, we'll employ the -[`TabBarView`]({{site.api}}/flutter/material/TabBarView-class.html) Widget. +Now that you have tabs, display content when a tab is selected. +For this purpose, use the +[`TabBarView`]({{site.api}}/flutter/material/TabBarView-class.html) widget. -*Note:* Order is important and must correspond to the order of the tabs in the -`TabBar`! +{{site.alert.note}} + Order is important and must correspond to the order of the tabs in the + `TabBar`. +{{site.alert.end}} ```dart diff --git a/src/docs/cookbook/design/themes.md b/src/docs/cookbook/design/themes.md index 5f11d5162c0..8b3bf879050 100644 --- a/src/docs/cookbook/design/themes.md +++ b/src/docs/cookbook/design/themes.md @@ -10,36 +10,37 @@ next: path: /docs/cookbook/design/fonts --- -In order to share colors and font styles throughout our app, we can take -advantage of themes. There are two ways to define themes: App-wide or using -`Theme` Widgets that define the colors and font styles for a particular part of -our application. In fact, app-wide themes are just `Theme` Widgets created at -the root of our apps by the `MaterialApp`! - -After we define a Theme, we can use it within our own Widgets. In addition, the -Material Widgets provided by Flutter will use our Theme to set the background +To share colors and font styles throughout an app, take +advantage of themes. There are two ways to define themes: +App-wide or using `Theme` widgets that define the colors and +font styles for a particular part of the application. In fact, +app-wide themes are just `Theme` widgets created at +the root of an apps by the `MaterialApp`. + +After defining a Theme, use it within your own widgets. Flutter's +Material widgets also use your Theme to set the background colors and font styles for AppBars, Buttons, Checkboxes, and more. ## Creating an app theme -In order to share a Theme containing colors and font styles across our entire -app, we can provide +To share a Theme across an entire app, provide a [`ThemeData`]({{site.api}}/flutter/material/ThemeData-class.html) to the `MaterialApp` constructor. -If no `theme` is provided, Flutter creates a fallback theme under the hood. +If no `theme` is provided, +Flutter creates a default theme behind the scenes. ```dart MaterialApp( title: title, theme: ThemeData( - // Define the default Brightness and Colors + // Define the default brightness and colors. brightness: Brightness.dark, primaryColor: Colors.lightBlue[800], accentColor: Colors.cyan[600], - // Define the default Font Family + // Define the default font family. fontFamily: 'Montserrat', // Define the default TextTheme. Use this to specify the default @@ -53,21 +54,21 @@ MaterialApp( ); ``` -Please see the [ThemeData]({{site.api}}/flutter/material/ThemeData-class.html) +See the [ThemeData]({{site.api}}/flutter/material/ThemeData-class.html) documentation to see all of the colors and fonts you can define. ## Themes for part of an application -If we want to override the app-wide theme in part of our application, we can -wrap a section of our app in a `Theme` Widget. +To override the app-wide theme in part of an application, +wrap a section of the app in a `Theme` widget. -There are two ways to approach this: creating unique `ThemeData`, or -extending the parent theme. +There are two ways to approach this: creating a unique `ThemeData`, +or extending the parent theme. ### Creating unique `ThemeData` -If we don't want to inherit any application colors or font styles, we can create -a `ThemeData()` instance and pass that to the `Theme` Widget. +If you don't want to inherit any application colors or font styles, +create a `ThemeData()` instance and pass that to the `Theme` widget. ```dart @@ -86,14 +87,14 @@ Theme( ### Extending the parent theme Rather than overriding everything, it often makes sense to extend the parent -theme. We can achieve this by using the -[`copyWith`]({{site.api}}/flutter/material/ThemeData/copyWith.html) +theme. You can achieve this by using the +[`copyWith()`]({{site.api}}/flutter/material/ThemeData/copyWith.html) method. ```dart Theme( - // Find and Extend the parent theme using "copyWith". Please see the next + // Find and extend the parent theme using "copyWith". See the next // section for more info on `Theme.of`. data: Theme.of(context).copyWith(accentColor: Colors.yellow), child: FloatingActionButton( @@ -105,15 +106,16 @@ Theme( ## Using a Theme -Now that we've defined a theme, we can use it within our Widget `build` methods -by using the `Theme.of(context)` function! +Now that you've defined a theme, use it within the widgets' `build()` +methods by using the `Theme.of(context)` function. -`Theme.of(context)` looks up the Widget tree and return the nearest `Theme` -in the tree. If we have a stand-alone `Theme` defined above our Widget, it -returns that. If not, it returns the App theme. +The `Theme.of(context)` method looks up the widget tree and returns +the nearest `Theme` in the tree. If you have a stand-alone +`Theme` defined above your widget, it returns that. +If not, it returns the app's theme. -In fact, the `FloatingActionButton` uses this exact technique to find the -`accentColor`! +In fact, the `FloatingActionButton` uses this technique to find the +`accentColor`. ```dart @@ -144,12 +146,12 @@ class MyApp extends StatelessWidget { return MaterialApp( title: appName, theme: ThemeData( - // Define the default Brightness and Colors + // Define the default brightness and colors. brightness: Brightness.dark, primaryColor: Colors.lightBlue[800], accentColor: Colors.cyan[600], - // Define the default Font Family + // Define the default font family. fontFamily: 'Montserrat', // Define the default TextTheme. Use this to specify the default diff --git a/src/docs/cookbook/forms/focus.md b/src/docs/cookbook/forms/focus.md index f92041f343d..6e75476f465 100644 --- a/src/docs/cookbook/forms/focus.md +++ b/src/docs/cookbook/forms/focus.md @@ -1,5 +1,5 @@ --- -title: Focus on a Text Field +title: Focus on a text field prev: title: Create and style a text field path: /docs/cookbook/forms/text-input @@ -9,22 +9,24 @@ next: --- When a text field is selected and accepting input, it is said to have "focus." -Generally, users can focus text fields by tapping on them, and developers -can focus text fields using the tools described in this recipe. +Generally, users shift focus to a text field by tapping, +and developers shift focus to a text field programmatically by +using the tools described in this recipe. Managing focus is a fundamental tool for creating forms with an intuitive -flow. For example, say we have a search screen with a text field. When -the user navigates to the search screen, we can focus the search term text field. +flow. For example, say you have a search screen with a text field. +When the user navigates to the search screen, +you can focus on the search term text field. This allows the user to start typing as soon as the screen -is visible, without needing to manually tap on the text field! +is visible, without needing to manually tap the text field. -In this recipe, we'll learn how to focus a text field as soon as it's visible -as well as how to focus a text field when a button is tapped. +In this recipe, learn how to focus on a text field as soon as it's visible, +as well as how to focus on a text field when a button is tapped. ## Focus a text field as soon as it's visible -In order to focus a text field as soon as it's visible, we can use the -`autofocus` property. +To focus on a text field as soon as it's visible, +use the `autofocus` property. ```dart @@ -33,15 +35,17 @@ TextField( ); ``` -For more information on handling input and creating text fields, please see the -[Forms section of the cookbook](/docs/cookbook#forms). +For more information on handling input and creating text fields, +see the [Forms](/docs/cookbook#forms) section of the cookbook. ## Focus a text field when a button is tapped -Rather than immediately focusing a specific text field, we might need to focus a -text field at a later point in time. In this example, we'll see how to focus a -text field after the user presses a button. In the real world, you may also need -to focus a specific text field in response to an api call or a validation error. +Rather than immediately focusing on a specific text field, +you might need to focus on a text field at a later point in time. +In this example, focus on a +text field after the user presses a button. +In the real world, you might also need to focus on a specific +text field in response to an API call or a validation error. ### Directions @@ -51,29 +55,29 @@ to focus a specific text field in response to an api call or a validation error. ### 1. Create a `FocusNode` -First, we'll need to create a +First, create a [`FocusNode`]({{site.api}}/flutter/widgets/FocusNode-class.html). -We will use the `FocusNode` to identify a specific `TextField` in Flutter's -"focus tree." This will allow us to focus the `TextField` in the next steps. +Use the `FocusNode` to identify a specific `TextField` in Flutter's +"focus tree." This allows you to focus the `TextField` in the next steps. -Since focus nodes are long-lived objects, we need to manage the lifecycle -using a `State` class. To do so, create the `FocusNode` instance inside the -`initState` method of a `State` class, and clean them up inside the `dispose` -method. +Since focus nodes are long-lived objects, manage the lifecycle +using a `State` object. To do so, create the `FocusNode` instance +inside the `initState()` method of a `State` class, +and clean it up inside the `dispose()` method. ```dart -// Define a Custom Form Widget +// Define a custom Form widget. class MyCustomForm extends StatefulWidget { @override _MyCustomFormState createState() => _MyCustomFormState(); } -// Define a corresponding State class. This class will hold the data related to -// the form. +// Define a corresponding State class. +// This class holds data related to the form. class _MyCustomFormState extends State { // Define the focus node. To manage the lifecycle, create the FocusNode in - // the initState method, and clean it up in the dispose method + // the initState method, and clean it up in the dispose method. FocusNode myFocusNode; @override @@ -85,7 +89,7 @@ class _MyCustomFormState extends State { @override void dispose() { - // Clean up the focus node when the Form is disposed + // Clean up the focus node when the Form is disposed. myFocusNode.dispose(); super.dispose(); @@ -93,15 +97,15 @@ class _MyCustomFormState extends State { @override Widget build(BuildContext context) { - // We will fill this out in the next step! + // Fill this out in the next step. } } ``` ### 2. Pass the `FocusNode` to a `TextField` -Now that we have our `FocusNode`, we can pass it to a specific `TextField` in -the `build` method. +Now that you have a `FocusNode`, +pass it to a specific `TextField` in the `build()` method. ```dart @@ -119,15 +123,15 @@ class _MyCustomFormState extends State { ### 3. Focus the `TextField` when a button is tapped -Finally, we'll want to focus the text field when the user taps a floating -action button! We'll use the -[`requestFocus`]({{site.api}}/flutter/widgets/FocusScopeNode/requestFocus.html) +Finally, focus the text field when the user taps a floating +action button. Use the +[`requestFocus()`]({{site.api}}/flutter/widgets/FocusScopeNode/requestFocus.html) method to achieve this task. ```dart FloatingActionButton( - // When the button is pressed, ask Flutter to focus our text field using + // When the button is pressed, focus on the text field using // myFocusNode. onPressed: () => FocusScope.of(context).requestFocus(myFocusNode), ); @@ -150,17 +154,17 @@ class MyApp extends StatelessWidget { } } -// Define a Custom Form Widget +// Define a custom Form widget. class MyCustomForm extends StatefulWidget { @override _MyCustomFormState createState() => _MyCustomFormState(); } -// Define a corresponding State class. This class will hold the data related to -// the form. +// Define a corresponding State class. +// This class holds data related to the form. class _MyCustomFormState extends State { // Define the focus node. To manage the lifecycle, create the FocusNode in - // the initState method, and clean it up in the dispose method + // the initState method, and clean it up in the dispose method. FocusNode myFocusNode; @override @@ -172,7 +176,7 @@ class _MyCustomFormState extends State { @override void dispose() { - // Clean up the focus node when the Form is disposed + // Clean up the focus node when the Form is disposed. myFocusNode.dispose(); super.dispose(); @@ -188,12 +192,12 @@ class _MyCustomFormState extends State { padding: const EdgeInsets.all(16.0), child: Column( children: [ - // The first text field will be focused as soon as the app starts + // The first text field is focused on as soon as the app starts. TextField( autofocus: true, ), - // The second text field will be focused when a user taps on the - // FloatingActionButton + // The second text field is focused on when a user taps the + // FloatingActionButton. TextField( focusNode: myFocusNode, ), @@ -201,8 +205,8 @@ class _MyCustomFormState extends State { ), ), floatingActionButton: FloatingActionButton( - // When the button is pressed, ask Flutter to focus our text field using - // myFocusNode. + // When the button is pressed, + // focus on the text field using myFocusNode. onPressed: () => FocusScope.of(context).requestFocus(myFocusNode), tooltip: 'Focus Second Text Field', child: Icon(Icons.edit), diff --git a/src/docs/cookbook/forms/retrieve-input.md b/src/docs/cookbook/forms/retrieve-input.md index 8094f691f12..7e055ba3bd6 100644 --- a/src/docs/cookbook/forms/retrieve-input.md +++ b/src/docs/cookbook/forms/retrieve-input.md @@ -4,12 +4,12 @@ prev: title: Handling changes to a text field path: /docs/cookbook/forms/text-field-changes next: - title: Adding Material Touch Ripples + title: Adding Material touch ripples path: /docs/cookbook/gestures/ripples --- -In this recipe, we'll see how to retrieve the text a user has typed into a text -field. +In this recipe, +learn how to retrieve the text a user has entered into a text field. ## Directions @@ -19,53 +19,53 @@ field. ## 1. Create a `TextEditingController` -In order to retrieve the text a user has typed into a text field, we need to -create a -[`TextEditingController`]({{site.api}}/flutter/widgets/TextEditingController-class.html). -We will then supply the `TextEditingController` to a `TextField` in the next -steps. +To retrieve the text a user has entered into a text field, create a +[`TextEditingController`]({{site.api}}/flutter/widgets/TextEditingController-class.html) +and supply it to a `TextField` in the next steps. Once a `TextEditingController` is supplied to a `TextField` or `TextFormField`, -we can use it to retrieve the text a user has typed into that text field. +use it to retrieve the text a user has entered into that text field. -Note: It is also important to `dispose` of the `TextEditingController` when we -are finished using it. This will ensure we discard any resources used by the -object. +{{site.alert.note}} + It is important to `dispose` of the `TextEditingController` when + finished using it. This ensures that you discard any resources used + by the object. +{{site.alert.end}} ```dart -// Define a Custom Form Widget +// Define a custom Form widget. class MyCustomForm extends StatefulWidget { @override _MyCustomFormState createState() => _MyCustomFormState(); } -// Define a corresponding State class. This class will hold the data related to -// our Form. +// Define a corresponding State class. +// This class holds the data related to the Form. class _MyCustomFormState extends State { - // Create a text controller. We will use it to retrieve the current value - // of the TextField! + // Create a text controller and use it to retrieve the current value + // of the TextField. final myController = TextEditingController(); @override void dispose() { - // Clean up the controller when the Widget is disposed + // Clean up the controller when the widget is disposed. myController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - // We will fill this out in the next step! + // Fill this out in the next step. } } ``` ## 2. Supply the `TextEditingController` to a `TextField` -Now that we have a `TextEditingController` to work with, we need to wire it up -to a specific text field. To do this, we'll supply the `TextEditingController` -to a `TextField` or `TextFormField` Widget as the `controller` property. +Now that you have a `TextEditingController`, wire it up +to a specific text field. To do this, supply the `TextEditingController` +to a `TextField` or `TextFormField` widget as the `controller` property. ```dart @@ -76,27 +76,27 @@ TextField( ## 3. Display the current value of the text field -After we've supplied the `TextEditingController` to our text field, we can begin -reading values! We will use the -[`text`]({{site.api}}/flutter/widgets/TextEditingController/text.html) -method provided by the `TextEditingController` to retrieve the String of text -the user has typed into the text field. +After supplying the `TextEditingController` to the text field, +begin reading values. Use the +[`text()`]({{site.api}}/flutter/widgets/TextEditingController/text.html) +method provided by the `TextEditingController` to retrieve the +String that the user has entered into the text field. -In this example, we will display an alert dialog with the current value of -the text field when the user taps on a floating action button. +In this example, display an alert dialog with the current value of +the text field when the user taps a floating action button. ```dart FloatingActionButton( - // When the user presses the button, show an alert dialog with the - // text the user has typed into our text field. + // When the user presses the button, show an alert dialog containing the + // text that the user has entered into the text field. onPressed: () { return showDialog( context: context, builder: (context) { return AlertDialog( - // Retrieve the text the user has typed in using our - // TextEditingController + // Retrieve the text the user has entered by using the + // TextEditingController. content: Text(myController.text), ); }, @@ -124,22 +124,22 @@ class MyApp extends StatelessWidget { } } -// Define a Custom Form Widget +// Define a custom Form widget. class MyCustomForm extends StatefulWidget { @override _MyCustomFormState createState() => _MyCustomFormState(); } -// Define a corresponding State class. This class will hold the data related to -// our Form. +// Define a corresponding State class. +// This class holds the data related to the Form. class _MyCustomFormState extends State { - // Create a text controller. We will use it to retrieve the current value - // of the TextField! + // Create a text controller and use it to retrieve the current value + // of the TextField. final myController = TextEditingController(); @override void dispose() { - // Clean up the controller when the Widget is disposed + // Clean up the controller when the widget is disposed. myController.dispose(); super.dispose(); } @@ -157,15 +157,15 @@ class _MyCustomFormState extends State { ), ), floatingActionButton: FloatingActionButton( - // When the user presses the button, show an alert dialog with the - // text the user has typed into our text field. + // When the user presses the button, show an alert dialog containing + // the text that the user has entered into the text field. onPressed: () { return showDialog( context: context, builder: (context) { return AlertDialog( - // Retrieve the text the user has typed in using our - // TextEditingController + // Retrieve the text the that user has entered by using the + // TextEditingController. content: Text(myController.text), ); }, diff --git a/src/docs/cookbook/forms/text-field-changes.md b/src/docs/cookbook/forms/text-field-changes.md index ea757686e58..91e969d1144 100644 --- a/src/docs/cookbook/forms/text-field-changes.md +++ b/src/docs/cookbook/forms/text-field-changes.md @@ -8,27 +8,27 @@ next: path: /docs/cookbook/forms/retrieve-input --- -In some cases, it can be handy to run a callback function every time the text -in a text field changes. For example, we might want to build a search screen -with autocomplete functionality. In this case, we would want to update the +In some cases, it's useful to run a callback function every time the text +in a text field changes. For example, you might want to build a search screen +with autocomplete functionality, and you want to update the results as the user types. -How can we run a callback function every time the text changes? With Flutter, -we have two options: +How to run a callback function every time the text changes? +With Flutter, you have two options: - 1. Supply an `onChanged` callback to a `TextField` + 1. Supply an `onChanged()` callback to a `TextField` 2. Use a `TextEditingController` -## 1. Supply an `onChanged` callback to a `TextField` +## 1. Supply an `onChanged()` callback to a `TextField` The simplest approach is to supply an -[`onChanged`]({{site.api}}/flutter/material/TextField/onChanged.html) +[`onChanged()`]({{site.api}}/flutter/material/TextField/onChanged.html) callback to a [`TextField`]({{site.api}}/flutter/material/TextField-class.html). -Whenever the text changes, the callback will be invoked. One downside to this -approach is it does not work with `TextFormField` Widgets. +Whenever the text changes, the callback is invoked. One downside to this +approach is that it doesn't work with `TextFormField` widgets. -In this example, we will print the current value of the text field to the +In this example, print the current value of the text field to the console every time the text changes. @@ -48,8 +48,8 @@ as the [`controller`]({{site.api}}/flutter/material/TextField/controller.html) property of the `TextField` or a `TextFormField`. -To be notified when the text changes, we can listen to the controller using its -[`addListener`]({{site.api}}/flutter/foundation/ChangeNotifier/addListener.html) +To be notified when the text changes, listen to the controller using the +[`addListener()`]({{site.api}}/flutter/foundation/ChangeNotifier/addListener.html) method. ### Directions @@ -61,47 +61,51 @@ method. ### Create a `TextEditingController` -First, we'll need to create a `TextEditingController`. In the subsequent steps, -we will supply the `TextEditingController` to a `TextField`. Once we've wired -these two classes together, we can listen for changes to the text field! +First, create a `TextEditingController`. In later steps, +you supply the `TextEditingController` to a `TextField`. +Once wiring these two classes together, +you can listen for changes to the text field. ```dart -// Define a Custom Form Widget +// Define a custom Form widget. class MyCustomForm extends StatefulWidget { @override _MyCustomFormState createState() => _MyCustomFormState(); } -// Define a corresponding State class. This class will hold the data related to -// our Form. +// Define a corresponding State class. +// This class holds data related to the Form. class _MyCustomFormState extends State { - // Create a text controller. We will use it to retrieve the current value - // of the TextField! + // Create a text controller and use it to retrieve the current value + // of the TextField. final myController = TextEditingController(); @override void dispose() { - // Clean up the controller when the Widget is removed from the Widget tree + // Clean up the controller when the widget is removed from the widget tree. myController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - // We will fill this out in the next step! + // Fill this out in the next step. } } ``` -Note: Please remember to `dispose` the `TextEditingController` when it is no -longer needed. This will ensure we discard any resources used by the object. +{{site.alert.note}} + Remember to `dispose()` of the `TextEditingController` when it's no + longer needed. + This ensures that you discard any resources used by the object. +{{site.alert.end}} ### Supply the `TextEditingController` to a `TextField` -In order to work, the `TextEditingController` must be supplied to either a -`TextField` or a `TextFormField`. Once it's wired up, we can begin listening -for changes to the text field. +For this to work, the `TextEditingController` must be supplied +to either a `TextField` or a `TextFormField`. Once it's wired up, +you can begin listening for changes to the text field. ```dart @@ -112,11 +116,10 @@ TextField( ### Create a function to print the latest value -Now, we'll need a function that should run every time the text changes! In this -example, we'll create a method that prints out the current value of the text -field. +Now, you need a function to run every time the text changes. +Create a method that prints out the current value of the text field. -This method will live inside our `_MyCustomFormState` class. +This method lives inside the `_MyCustomFormState` class. ```dart @@ -127,12 +130,12 @@ _printLatestValue() { ### Listen to the controller for changes -Finally, we need to listen to the `TextEditingController` and run the -`_printLatestValue` method whenever the text changes. We will use the -[`addListener`]({{site.api}}/flutter/foundation/ChangeNotifier/addListener.html) +Finally, listen to the `TextEditingController` and run the +`_printLatestValue()` method when the text changes. Use the +[`addListener()`]({{site.api}}/flutter/foundation/ChangeNotifier/addListener.html) method to achieve this task. -In this example, we will begin listening for changes when the +Begin listening for changes when the `_MyCustomFormState` class is initialized, and stop listening when the `_MyCustomFormState` is disposed. @@ -143,7 +146,7 @@ class _MyCustomFormState extends State { void initState() { super.initState(); - // Start listening to changes + // Start listening to changes. myController.addListener(_printLatestValue); } } @@ -166,17 +169,17 @@ class MyApp extends StatelessWidget { } } -// Define a Custom Form Widget +// Define a custom Form widget. class MyCustomForm extends StatefulWidget { @override _MyCustomFormState createState() => _MyCustomFormState(); } -// Define a corresponding State class. This class will hold the data related to -// our Form. +// Define a corresponding State class. +// This class holds data related to the Form. class _MyCustomFormState extends State { - // Create a text controller. We will use it to retrieve the current value - // of the TextField! + // Create a text controller and use it to retrieve the current value + // of the TextField. final myController = TextEditingController(); @override @@ -188,8 +191,8 @@ class _MyCustomFormState extends State { @override void dispose() { - // Clean up the controller when the Widget is removed from the Widget tree - // This also removes the _printLatestValue listener + // Clean up the controller when the widget is removed from the widget tree. + // This also removes the _printLatestValue listener. myController.dispose(); super.dispose(); } diff --git a/src/docs/cookbook/forms/text-input.md b/src/docs/cookbook/forms/text-input.md index 5868b540b53..daca4cd3add 100644 --- a/src/docs/cookbook/forms/text-input.md +++ b/src/docs/cookbook/forms/text-input.md @@ -4,13 +4,14 @@ prev: title: Building a form with validation path: /docs/cookbook/forms/validation next: - title: Focus on a Text Field + title: Focus on a text field path: /docs/cookbook/forms/focus --- -Text fields allow users to type text into our apps. Text fields can be used to -build forms, messaging apps, search experiences, and more! In this recipe, -we'll explore how to create and style text fields. +Text fields allow users to type text into an app. +Text fields are used to build forms, +send messages, create search experiences, and more. +In this recipe, explore how to create and style text fields. Flutter provides two text fields out of the box: [`TextField`]({{site.api}}/flutter/material/TextField-class.html) @@ -21,20 +22,19 @@ and [`TextFormField`]({{site.api}}/flutter/material/TextFormField-class.html). [`TextField`]({{site.api}}/flutter/material/TextField-class.html) is the most commonly used text input widget. -By default, a `TextField` is decorated with an underline. We can add a label, -an icon, inline hint text, and error text by supplying an +By default, a `TextField` is decorated with an underline. +You can add a label, icon, inline hint text, and error text by supplying an [`InputDecoration`]({{site.api}}/flutter/material/InputDecoration-class.html) as the [`decoration`]({{site.api}}/flutter/material/TextField/decoration.html) property of the `TextField`. To remove the decoration entirely (including the -underline and the space reserved for the label), set the `decoration` to null -explicitly. +underline and the space reserved for the label), set the `decoration` to null. ```dart TextField( decoration: InputDecoration( border: InputBorder.none, - hintText: 'Please enter a search term' + hintText: 'Enter a search term' ), ); ``` @@ -57,5 +57,5 @@ TextFormField( ); ``` -For more information on input validation, please see the +For more information on input validation, see the [Building a form with validation](/docs/cookbook/forms/validation/) recipe. diff --git a/src/docs/cookbook/forms/validation.md b/src/docs/cookbook/forms/validation.md index 0b0536e0875..f4fddaec03f 100644 --- a/src/docs/cookbook/forms/validation.md +++ b/src/docs/cookbook/forms/validation.md @@ -1,25 +1,25 @@ --- title: Building a form with validation prev: - title: Working with Tabs + title: Working with tabs path: /docs/cookbook/design/tabs next: title: Create and style a text field path: /docs/cookbook/forms/text-input --- -Apps often require users to enter information into a text field. For -example, we might be working on an app that requires our users to log in with an -email address and password combination. +Apps often require users to enter information into a text field. +For example, you might require users to log in with an email address +and password combination. -In order to make our apps secure and easy to use, we can check whether the +To make apps secure and easy to use, check whether the information the user has provided is valid. If the user has correctly filled -out the form, we can process the information. If the user submits incorrect -information, we can display a friendly error message letting them know what went +out the form, process the information. If the user submits incorrect +information, display a friendly error message letting them know what went wrong. -In this example, we'll see how to add validation to a form with a single -text field. +In this example, learn how to add validation to a form with +a single text field. ## Directions @@ -29,18 +29,19 @@ text field. ## 1. Create a `Form` with a `GlobalKey` -First, we'll need a [`Form`]({{site.api}}/flutter/widgets/Form-class.html) -to work with. The `Form` Widget acts as a container to group and validate -multiple form fields. +First, create a +[`Form`]({{site.api}}/flutter/widgets/Form-class.html). +The `Form` widget acts as a container for grouping +and validating multiple form fields. -When we create the form, we'll also need to provide a +When creating the form, provide a [`GlobalKey`]({{site.api}}/flutter/widgets/GlobalKey-class.html). -This will uniquely identify the `Form` that we're working with, and will allow -us to validate the form in a later step. +This uniquely identifies the `Form`, +and allows validation of the form in a later step. ```dart -// Define a Custom Form Widget +// Define a custom Form widget. class MyCustomForm extends StatefulWidget { @override MyCustomFormState createState() { @@ -48,57 +49,58 @@ class MyCustomForm extends StatefulWidget { } } -// Define a corresponding State class. This class will hold the data related to -// the form. +// Define a corresponding State class. +// This class holds data related to the form. class MyCustomFormState extends State { - // Create a global key that will uniquely identify the Form widget and allow - // us to validate the form + // Create a global key that uniquely identifies the Form widget + // and allows validation of the form. // - // Note: This is a `GlobalKey`, not a GlobalKey! + // Note: This is a `GlobalKey`, + // not a GlobalKey. final _formKey = GlobalKey(); @override Widget build(BuildContext context) { - // Build a Form widget using the _formKey we created above + // Build a Form widget using the _formKey created above. return Form( key: _formKey, - child: // We'll build this out in the next steps! + child: // Build this out in the next steps. ); } } ``` {{site.alert.tip}} -Using a `GlobalKey` is the recommended way to access a form. However, if you -have a more complex widget tree, you can use the -[`Form.of`]({{site.api}}/flutter/widgets/Form/of.html) method to -access the form within nested widgets. + Using a `GlobalKey` is the recommended way to access a form. + However, if you have a more complex widget tree, you can use the + [`Form.of()`]({{site.api}}/flutter/widgets/Form/of.html) method to + access the form within nested widgets. {{site.alert.end}} ## 2. Add a `TextFormField` with validation logic -We have our `Form` in place, but we haven't provided a way for our users to -enter text! This is the job of a +You have the `Form` in place, +but you haven't provided a way for users to enter text. +This is the job of a [`TextFormField`]({{site.api}}/flutter/material/TextFormField-class.html). -The `TextFormField` Widget renders a material design text input and knows how to -display validation errors when they occur. +The `TextFormField` widget renders a material design text field +and knows how to display validation errors when they occur. -How can we validate the input? By providing a `validator` function to the +How to validate the input? By providing a `validator()` function to the `TextFormField`. If there is an error with the information the user has -provided, the `validator` function must return a `String` containing -an error message. If there are no errors, the function should not return -anything. +provided, the `validator` function returns a `String` containing +an error message. If there are no errors, the function returns nothing. -In this example, we will create a `validator` that ensures the `TextFormField` -isn't empty. If it is empty, we will return a friendly error message! +For this example, create a `validator` that ensures the `TextFormField` +isn't empty. If it is empty, return a friendly error message. ```dart TextFormField( - // The validator receives the text the user has typed in + // The validator receives the text that the user has entered. validator: (value) { if (value.isEmpty) { - return 'Please enter some text'; + return 'Enter some text'; } }, ); @@ -106,22 +108,22 @@ TextFormField( ## 3. Create a button to validate and submit the form -Now that we have a form with a text field, we'll need to provide a button the -user can tap to submit the information. +Now that you have a form with a text field, +provide a button that the user can tap to submit the information. -When the user attempts to submit the form, we'll need to check if the form is -valid. If it is, we will show a success message. If the text field has no -content, we'll want to display the error message. +When the user attempts to submit the form, check if the form is valid. +If it is, show a success message. If the text field has no content, +display the error message. ```dart RaisedButton( onPressed: () { - // Validate will return true if the form is valid, or false if - // the form is invalid. + // Validate returns true if the form is valid, otherwise false. if (_formKey.currentState.validate()) { - // If the form is valid, display a snackbar. In the real world, you'd - // often want to call a server or save the information in a database + // If the form is valid, display a snackbar. In the real world, + // you'd often call a server or save the information in a database. + Scaffold .of(context) .showSnackBar(SnackBar(content: Text('Processing Data'))); @@ -133,16 +135,17 @@ RaisedButton( ### How does this work? -In order to validate the form, we'll need to use the `_formKey` created in -step 1. We can use the `_formKey.currentState` method to access the +To validate the form, use the `_formKey` created in +step 1. You can use the `_formKey.currentState()` method to access the [`FormState`]({{site.api}}/flutter/widgets/FormState-class.html), -which is automatically created by Flutter when we build a `Form`. +which is automatically created by Flutter when building a `Form`. -The `FormState` class contains the `validate` method. When the `validate` method -is called, it will run the `validator` function for each text field in the form. -If everything looks good, the method returns `true`. If any text field contains -errors, it will display the error message for each invalid text field and return -`false`. +The `FormState` class contains the `validate()` method. +When the `validate()` method is called, it runs the `validator()` +function for each text field in the form. +If everything looks good, the method returns `true`. +If any text field contains errors, it displays the error message +for each invalid text field and returns `false`. ## Complete example @@ -168,7 +171,7 @@ class MyApp extends StatelessWidget { } } -// Create a Form Widget +// Create a Form widget. class MyCustomForm extends StatefulWidget { @override MyCustomFormState createState() { @@ -176,18 +179,19 @@ class MyCustomForm extends StatefulWidget { } } -// Create a corresponding State class. This class will hold the data related to -// the form. +// Create a corresponding State class. +// This class holds data related to the form. class MyCustomFormState extends State { - // Create a global key that will uniquely identify the Form widget and allow - // us to validate the form + // Create a global key that uniquely identifies the Form widget + // and allows validation of the form. // - // Note: This is a GlobalKey, not a GlobalKey! + // Note: This is a GlobalKey, + // not a GlobalKey. final _formKey = GlobalKey(); @override Widget build(BuildContext context) { - // Build a Form widget using the _formKey we created above + // Build a Form widget using the _formKey created above. return Form( key: _formKey, child: Column( @@ -196,7 +200,7 @@ class MyCustomFormState extends State { TextFormField( validator: (value) { if (value.isEmpty) { - return 'Please enter some text'; + return 'Enter some text'; } }, ), @@ -204,10 +208,10 @@ class MyCustomFormState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: RaisedButton( onPressed: () { - // Validate will return true if the form is valid, or false if - // the form is invalid. + // Validate returns true if the form is valid, or false + // otherwise. if (_formKey.currentState.validate()) { - // If the form is valid, we want to show a Snackbar + // If the form is valid, show a Snackbar. Scaffold.of(context) .showSnackBar(SnackBar(content: Text('Processing Data'))); } diff --git a/src/docs/cookbook/gestures/dismissible.md b/src/docs/cookbook/gestures/dismissible.md index 9af070e0ad6..8fa0c4f226e 100644 --- a/src/docs/cookbook/gestures/dismissible.md +++ b/src/docs/cookbook/gestures/dismissible.md @@ -1,47 +1,48 @@ --- -title: Implement Swipe to Dismiss +title: Implement swipe to dismiss prev: - title: Handling Taps + title: Handling taps path: /docs/cookbook/gestures/handling-taps next: title: Display images from the internet path: /docs/cookbook/images/network-image --- -The "Swipe to dismiss" pattern is common in many mobile apps. For example, if -we're writing an email app, we might want to allow our users to swipe away email -messages in a list. When they do, we'll want to move the item from the Inbox to -the Trash. +The "swipe to dismiss" pattern is common in many mobile apps. For example, +if writing an email app, you might want to allow a user to swipe away +email messages to delete them from a list. When a list item is swiped, +move the message from the Inbox to the Trash. Flutter makes this task easy by providing the -[`Dismissible`]({{site.api}}/flutter/widgets/Dismissible-class.html) Widget. +[`Dismissible`]({{site.api}}/flutter/widgets/Dismissible-class.html) widget. ## Directions - 1. Create List of Items - 2. Wrap each item in a `Dismissible` Widget - 3. Provide "Leave Behind" indicators + 1. Create list of items + 2. Wrap each item in a `Dismissible` widget + 3. Provide "leave behind" indicators -## 1. Create List of Items +## 1. Create list of items -First, we'll create a list of items we can swipe away. For more detailed -instructions on how to create a list, please follow the [Working with long +First, create a list of items. For more detailed +instructions on how to create a list, follow the [Working with long lists](/docs/cookbook/lists/long-lists/) recipe. -### Create a Data Source +### Create a data source -In our example, we'll want 20 sample items to work with. To keep it simple, -we'll generate a List of Strings. +In this example, +you want 20 sample items to work with. +To keep it simple, generate a list of strings. ```dart final items = List.generate(20, (i) => "Item ${i + 1}"); ``` -### Convert the data source into a List +### Convert the data source into a list -At first, we'll simply display each item in the List on screen. Users will -not be able to swipe away with these items just yet! +Display each item in the list on screen. Users won't +be able to swipe these items away just yet. ```dart @@ -53,36 +54,36 @@ ListView.builder( ); ``` -## 2. Wrap each item in a Dismissible Widget +## 2. Wrap each item in a Dismissible widget -Now that we're displaying a list of items, we'll want to give our users the -ability to swipe each item off the list! +Now that you have a list of items, +give users the ability to swipe each item off the list. -After the user has swiped away the item, we'll need to run some code to remove -the item from the list and display a Snackbar. In a real app, you might need to -perform more complex logic, such as removing the item from a web service or -database. +After the user has swiped away the item, remove +the item from the list and display a Snackbar. +In a real app, you might need to perform more complex logic, +such as removing the item from a web service or database. This is where the [`Dismissible`]({{site.api}}/flutter/widgets/Dismissible-class.html) -Widget comes into play! In our example, we'll update our `itemBuilder` function -to return a `Dismissible` Widget. +widget comes into play. Update the `itemBuilder` function +to return a `Dismissible` widget. ```dart Dismissible( // Each Dismissible must contain a Key. Keys allow Flutter to - // uniquely identify Widgets. + // uniquely identify widgets. key: Key(item), - // We also need to provide a function that will tell our app + // Provide a function that tells the app // what to do after an item has been swiped away. onDismissed: (direction) { - // Remove the item from our data source. + // Remove the item from the data source. setState(() { items.removeAt(index); }); - // Show a snackbar! This snackbar could also contain "Undo" actions. + // Show a snackbar. This snackbar could also contain "Undo" actions. Scaffold .of(context) .showSnackBar(SnackBar(content: Text("$item dismissed"))); @@ -91,19 +92,21 @@ Dismissible( ); ``` -## 3. Provide "Leave Behind" indicators +## 3. Provide "leave behind" indicators -As it stands, our app allows users to swipe items off the List, but it might -not give them a visual indication of what happens when they do. To provide a cue -that we're removing items, we'll display a "Leave Behind" indicator as they -swipe the item off the screen. In this case, a red background! +As it stands, +the app allows users to swipe items off the list, but it doesn't +give a visual indication of what happens when they do. +To provide a cue that items are removed, +display a "leave behind" indicator as they +swipe the item off the screen. In this case, a red background. -For this purpose, we'll provide a `background` parameter to the `Dismissible`. +Provide a `background` parameter to the `Dismissible`. ```dart Dismissible( - // Show a red background as the item is swiped away + // Show a red background as the item is swiped away. background: Container(color: Colors.red), key: Key(item), onDismissed: (direction) { @@ -129,8 +132,8 @@ void main() { runApp(MyApp()); } -// MyApp is a StatefulWidget. This allows us to update the state of the -// Widget whenever an item is removed. +// MyApp is a StatefulWidget. This allows updating the state of the +// widget when an item is removed. class MyApp extends StatefulWidget { MyApp({Key key}) : super(key: key); @@ -163,21 +166,21 @@ class MyAppState extends State { return Dismissible( // Each Dismissible must contain a Key. Keys allow Flutter to - // uniquely identify Widgets. + // uniquely identify widgets. key: Key(item), - // We also need to provide a function that tells our app + // Provide a function that tells the app // what to do after an item has been swiped away. onDismissed: (direction) { - // Remove the item from our data source. + // Remove the item from the data source. setState(() { items.removeAt(index); }); - // Then show a snackbar! + // Then show a snackbar. Scaffold.of(context) .showSnackBar(SnackBar(content: Text("$item dismissed"))); }, - // Show a red background as the item is swiped away + // Show a red background as the item is swiped away. background: Container(color: Colors.red), child: ListTile(title: Text('$item')), ); diff --git a/src/docs/cookbook/gestures/handling-taps.md b/src/docs/cookbook/gestures/handling-taps.md index 62473e43a39..fc1624444c8 100644 --- a/src/docs/cookbook/gestures/handling-taps.md +++ b/src/docs/cookbook/gestures/handling-taps.md @@ -1,38 +1,37 @@ --- -title: Handling Taps +title: Handling taps prev: - title: Adding Material Touch Ripples + title: Adding Material touch ripples path: /docs/cookbook/gestures/ripples next: - title: Implement Swipe to Dismiss + title: Implement swipe to dismiss path: /docs/cookbook/gestures/dismissible --- -We not only want to display information to our users, we want our users to -interact with our apps! So how do we respond to fundamental actions such as -tapping and dragging? We'll use the +You not only want to display information to users, +you want users to interact with your app. Use the [`GestureDetector`]({{site.api}}/flutter/widgets/GestureDetector-class.html) -Widget! +widget to respond to fundamental actions, such as tapping and dragging. -Say we want to make a custom button that shows a snackbar when tapped. How would -we approach this? +Say you want to make a custom button that shows a snackbar when tapped. +How to do this? ## Directions 1. Create the button - 2. Wrap it in a `GestureDetector` with an `onTap` callback + 2. Wrap it in a `GestureDetector` with an `onTap()` callback ```dart -// Our GestureDetector wraps our button +// The GestureDetector wraps the button. GestureDetector( - // When the child is tapped, show a snackbar + // When the child is tapped, show a snackbar. onTap: () { final snackBar = SnackBar(content: Text("Tap")); Scaffold.of(context).showSnackBar(snackBar); }, - // Our Custom Button! + // The custom button child: Container( padding: EdgeInsets.all(12.0), decoration: BoxDecoration( @@ -46,14 +45,13 @@ GestureDetector( ## Notes - 1. If you'd like to add the Material Ripple effect to your button, please - see the "[Adding Material Touch ripples](/docs/cookbook/gestures/ripples/)" recipe. - 2. While we've created a custom button to demonstrate these concepts, Flutter - includes a handful of buttons out of the box: - [RaisedButton]({{site.api}}/flutter/material/RaisedButton-class.html), - [FlatButton]({{site.api}}/flutter/material/FlatButton-class.html), - and [CupertinoButton]({{site.api}}/flutter/cupertino/CupertinoButton-class.html) - + 1. To add the Material ripple effect to your button, see the + [Adding Material touch ripples](/docs/cookbook/gestures/ripples/) recipe. + 2. While this example creates a custom button to demonstrate these concepts, + Flutter includes a handful of buttons out of the box: + [RaisedButton]({{site.api}}/flutter/material/RaisedButton-class.html), + [FlatButton]({{site.api}}/flutter/material/FlatButton-class.html), and + [CupertinoButton]({{site.api}}/flutter/cupertino/CupertinoButton-class.html) ## Complete example @@ -93,15 +91,15 @@ class MyHomePage extends StatelessWidget { class MyButton extends StatelessWidget { @override Widget build(BuildContext context) { - // Our GestureDetector wraps our button + // The GestureDetector wraps the button. return GestureDetector( - // When the child is tapped, show a snackbar + // When the child is tapped, show a snackbar. onTap: () { final snackBar = SnackBar(content: Text("Tap")); Scaffold.of(context).showSnackBar(snackBar); }, - // Our Custom Button! + // The custom button child: Container( padding: EdgeInsets.all(12.0), decoration: BoxDecoration( diff --git a/src/docs/cookbook/gestures/ripples.md b/src/docs/cookbook/gestures/ripples.md index dc4b12825a7..0e98f2b3b45 100644 --- a/src/docs/cookbook/gestures/ripples.md +++ b/src/docs/cookbook/gestures/ripples.md @@ -1,29 +1,31 @@ --- -title: Adding Material Touch Ripples +title: Adding Material touch ripples prev: title: Retrieve the value of a text field path: /docs/cookbook/forms/retrieve-input next: - title: Handling Taps + title: Handling taps path: /docs/cookbook/gestures/handling-taps --- -While designing an app that should follow the Material Design Guidelines, we'll -want to add the ripple animation to Widgets when tapped. +When designing an app that follows the Material Design Guidelines, +you'll want to add the ripple animation to widgets when tapped. -Flutter provides the [`InkWell`]({{site.api}}/flutter/material/InkWell-class.html) -Widget to achieve this effect. +Flutter provides the +[`InkWell`]({{site.api}}/flutter/material/InkWell-class.html) +widget to achieve this effect. ## Directions - 1. Create a Widget we want to tap - 2. Wrap it in an `InkWell` Widget to manage tap callbacks and ripple animations + 1. Create a widget that supports tap + 2. Wrap it in an `InkWell` widget to manage tap callbacks and + ripple animations ```dart -// The InkWell Wraps our custom flat button Widget +// The InkWell wraps the custom flat button widget. InkWell( - // When the user taps the button, show a snackbar + // When the user taps the button, show a snackbar. onTap: () { Scaffold.of(context).showSnackBar(SnackBar( content: Text('Tap'), @@ -74,9 +76,9 @@ class MyHomePage extends StatelessWidget { class MyButton extends StatelessWidget { @override Widget build(BuildContext context) { - // The InkWell Wraps our custom flat button Widget + // The InkWell wraps the custom flat button widget. return InkWell( - // When the user taps the button, show a snackbar + // When the user taps the button, show a snackbar. onTap: () { Scaffold.of(context).showSnackBar(SnackBar( content: Text('Tap'), diff --git a/src/docs/cookbook/images/cached-images.md b/src/docs/cookbook/images/cached-images.md index 96bc4dda001..e8439a543d9 100644 --- a/src/docs/cookbook/images/cached-images.md +++ b/src/docs/cookbook/images/cached-images.md @@ -4,12 +4,12 @@ prev: title: Fade in images with a placeholder path: /docs/cookbook/images/fading-in-images next: - title: Basic List + title: Basic list path: /docs/cookbook/lists/basic-list --- -In some cases, it can be handy to cache images as they're downloaded from the -web so they can be used offline. For this purpose, you'll employ the +In some cases, it's handy to cache images as they're downloaded from the +web so they can be used offline. For this purpose, use the [`cached_network_image`]({{site.pub-pkg}}/cached_network_image) package. In addition to caching, the cached_image_network package also supports @@ -24,8 +24,8 @@ CachedNetworkImage( ## Adding a placeholder -The `cached_network_image` package allows you to use any Widget as a -placeholder. In this example, you'll display a spinner while the image loads. +The `cached_network_image` package allows you to use any widget as a +placeholder. In this example, display a spinner while the image loads. ```dart diff --git a/src/docs/cookbook/images/fading-in-images.md b/src/docs/cookbook/images/fading-in-images.md index 0df98093655..12dd151d8e3 100644 --- a/src/docs/cookbook/images/fading-in-images.md +++ b/src/docs/cookbook/images/fading-in-images.md @@ -12,17 +12,17 @@ When displaying images using the default `Image` widget, you might notice they simply pop onto the screen as they're loaded. This might feel visually jarring to your users. -Instead, wouldn't it be nice if you could display a placeholder at first, -and images would fade in as they're loaded? You can use the +Instead, wouldn't it be nice to display a placeholder at first, +and images would fade in as they're loaded? Use the [`FadeInImage`]({{site.api}}/flutter/widgets/FadeInImage-class.html) -Widget packaged with Flutter for exactly this purpose. +widget packaged with Flutter for exactly this purpose. -`FadeInImage` works with images of any type: in-memory, local assets, or images -from the internet. +`FadeInImage` works with images of any type: in-memory, local assets, +or images from the internet. ## In-Memory -In this example, you'll use the +In this example, use the [transparent_image]({{site.pub-pkg}}/transparent_image) package for a simple transparent placeholder. @@ -76,9 +76,10 @@ class MyApp extends StatelessWidget { ### From asset bundle -You can also consider using local assets for placeholders. First, add the asset -to the project’s `pubspec.yaml` file (for more details see -[Assets and images](/docs/development/ui/assets-and-images)): +You can also consider using local assets for placeholders. +First, add the asset to the project’s `pubspec.yaml` file +(for more details, see +[Adding assets and images](/docs/development/ui/assets-and-images)): ```diff diff --git a/src/docs/cookbook/images/network-image.md b/src/docs/cookbook/images/network-image.md index b6690a5f4e0..d1277d71ab8 100644 --- a/src/docs/cookbook/images/network-image.md +++ b/src/docs/cookbook/images/network-image.md @@ -1,7 +1,7 @@ --- title: Display images from the internet prev: - title: Implement Swipe to Dismiss + title: Implement swipe to dismiss path: /docs/cookbook/gestures/dismissible next: title: Fade in images with a placeholder @@ -9,11 +9,11 @@ next: --- Displaying images is fundamental for most mobile apps. Flutter provides the -[`Image`]({{site.api}}/flutter/widgets/Image-class.html) Widget to +[`Image`]({{site.api}}/flutter/widgets/Image-class.html) widget to display different types of images. -In order to work with images from a URL, use the -[`Image.network`]({{site.api}}/flutter/widgets/Image/Image.network.html) +To work with images from a URL, use the +[`Image.network()`]({{site.api}}/flutter/widgets/Image/Image.network.html) constructor. @@ -25,8 +25,8 @@ Image.network( ## Bonus: Animated Gifs -One amazing thing about the `Image` Widget: It also supports animated gifs out -of the box! +One useful thing about the `Image` widget: +It also supports animated gifs out of the box. ```dart @@ -35,11 +35,11 @@ Image.network( ); ``` -## Placeholders and Caching +## Placeholders and caching -The default `Image.network` constructor does not handle more advanced -functionality, such as fading images in after loading or caching images -to the device after they're downloaded. To achieve these tasks, please see +The default `Image.network` constructor doesn't handle more advanced +functionality, such as fading images in after loading, or caching images +to the device after they're downloaded. To achieve these tasks, see the following recipes: * [Fade in images with a placeholder](/docs/cookbook/images/fading-in-images/) diff --git a/src/docs/cookbook/lists/basic-list.md b/src/docs/cookbook/lists/basic-list.md index 66a24d96eb1..f16c9ae9819 100644 --- a/src/docs/cookbook/lists/basic-list.md +++ b/src/docs/cookbook/lists/basic-list.md @@ -1,5 +1,5 @@ --- -title: Basic List +title: Basic list prev: title: Working with cached images path: /docs/cookbook/images/cached-images @@ -10,14 +10,14 @@ next: Displaying lists of data is a fundamental pattern for mobile apps. Flutter includes the [`ListView`]({{site.api}}/flutter/widgets/ListView-class.html) -Widget to make working with lists a breeze! +widget to make working with lists a breeze. ## Create a ListView -Using the standard `ListView` constructor is perfect for lists that contain only -a few items. We will also employ the built-in +Using the standard `ListView` constructor is perfect for lists that +contain only a few items. Use the built-in [`ListTile`]({{site.api}}/flutter/material/ListTile-class.html) -Widget to give our items a visual structure. +widget to give items a visual structure. ```dart diff --git a/src/docs/cookbook/lists/floating-app-bar.md b/src/docs/cookbook/lists/floating-app-bar.md index 997b5243d99..fbaf255e998 100644 --- a/src/docs/cookbook/lists/floating-app-bar.md +++ b/src/docs/cookbook/lists/floating-app-bar.md @@ -8,22 +8,22 @@ next: path: /docs/cookbook/lists/long-lists --- -To make it easier for users to view a list of items, you may want to hide the +To make it easier for users to view a list of items, you might want to hide the app bar as the user scrolls down the list. This is especially true if your app displays a "tall" app bar that occupies a lot of vertical space. Traditionally, you create an app bar by providing an `appBar` property to the -`Scaffold` Widget. This creates a fixed app bar that always remains above +`Scaffold` widget. This creates a fixed app bar that always remains above the `body` of the `Scaffold`. -Moving the app bar from a `Scaffold` Widget into a +Moving the app bar from a `Scaffold` widget into a [`CustomScrollView`]({{site.api}}/flutter/widgets/CustomScrollView-class.html) allows you to create an app bar that scrolls offscreen as you scroll through a list of items contained inside the `CustomScrollView`. This recipe demonstrates how to use a `CustomScrollView` to display a list of -items with an app bar on top that scrolls offscreen as the user scrolls down the -list. +items with an app bar on top that scrolls offscreen as the user scrolls +down the list. ### Directions @@ -33,28 +33,29 @@ list. ## 1. Create a `CustomScrollView` -In order to create a floating app bar, you need to place the app bar inside a -`CustomScrollView` that also contains the list of items. This synchronizes the -scroll position of the app bar and the list of items. You may think of the -`CustomScrollView` Widget as a `ListView` that allows you to mix and match -different types of scrollable lists and widgets together! +To create a floating app bar, place the app bar inside a +`CustomScrollView` that also contains the list of items. +This synchronizes the scroll position of the app bar and the list of items. +You might think of the `CustomScrollView` widget as a `ListView` +that allows you to mix and match different types of scrollable lists +and widgets together. -The scrollable lists and widgets that can be provided to the the -`CustomScrollView` are known as slivers. There are several types of Slivers, -such as a `SliverList`, `SliverGridList`, and `SliverAppBar`! In fact, the -`ListView` and `GridView` Widgets use the `SliverList` and `SliverGrid` widgets -under the hood! +The scrollable lists and widgets provided to the the +`CustomScrollView` are known as _slivers_. There are several types of Slivers, +such as a `SliverList`, `SliverGridList`, and `SliverAppBar`. +In fact, the `ListView` and `GridView` widgets use the `SliverList` and +`SliverGrid` widgets behind the scenes. For this example, create a `CustomScrollView` that contains a -`SliverAppBar` and a `SliverList`. In addition, you need to remove any app bars -you may be providing to the `Scaffold` Widget! +`SliverAppBar` and a `SliverList`. In addition, remove any app bars +you might be providing to the `Scaffold` widget. ```dart Scaffold( - // No appBar property provided, only the body! + // No appBar property provided, only the body. body: CustomScrollView( - // Add the app bar and list of items as slivers in the next steps + // Add the app bar and list of items as slivers in the next steps. slivers: [] ), ); @@ -66,7 +67,7 @@ Next, add an app bar to the [`CustomScrollView`]({{site.api}}/flutter/widgets/CustomScrollView-class.html). Flutter provides the [`SliverAppBar`]({{site.api}}/flutter/material/SliverAppBar-class.html) -Widget out of the box. Much like the normal `AppBar` widget, you can use the +widget out of the box. Much like the normal `AppBar` widget, use the `SliverAppBar` to display a title, tabs, images and more. However, the `SliverAppBar` also gives you the ability to create a "floating" @@ -76,9 +77,10 @@ you can configure the `SliverAppBar` to shrink and expand as the user scrolls. To achieve this effect: 1. Start with an app bar that displays only a title - 2. Set the `floating` property to `true`. This allows users to quickly reveal - the app bar when they being scrolling up the list. - 3. Add a `flexibleSpace` widget that will fill the available `expandedHeight`. + 2. Set the `floating` property to `true`. + This allows users to quickly reveal the app bar when they being + scrolling up the list. + 3. Add a `flexibleSpace` widget that fills the available `expandedHeight`. ```dart @@ -87,11 +89,11 @@ CustomScrollView( SliverAppBar( title: Text('Floating app bar'), // Allows the user to reveal the app bar if they begin scrolling back - // up the list of items + // up the list of items. floating: true, - // Display a placeholder Widget to visualize the shrinking size + // Display a placeholder widget to visualize the shrinking size. flexibleSpace: Placeholder(), - // Make the initial height of the SliverAppBar larger than normal + // Make the initial height of the SliverAppBar larger than normal. expandedHeight: 200, ), ], @@ -99,11 +101,11 @@ CustomScrollView( ``` {{site.alert.tip}} -Play around with the [various properties you can pass to the `SliverAppBar` -Widget]({{site.api}}/flutter/material/SliverAppBar/SliverAppBar.html) -and use hot reload to see the results. For example, you can use an `Image` -Widget for the `flexibleSpace` property to create a background image that -shrinks in size as it's scrolled offscreen. + Play around with the [various properties you can pass to the `SliverAppBar` + widget]({{site.api}}/flutter/material/SliverAppBar/SliverAppBar.html) + and use hot reload to see the results. For example, use an `Image` + widget for the `flexibleSpace` property to create a background image that + shrinks in size as it's scrolled offscreen. {{site.alert.end}} @@ -113,25 +115,26 @@ Now that you have the app bar in place, add a list of items to the `CustomScrollView`. You have two options: a [`SliverList`]({{site.api}}/flutter/widgets/SliverList-class.html) or a [`SliverGrid`]({{site.api}}/flutter/widgets/SliverGrid-class.html). -If you need to display a list of items one after the other, use the `SliverList` -Widget. If you need to display a grid list, use the `SliverGrid` Widget. +If you need to display a list of items one after the other, +use the `SliverList` widget. If you need to display a grid list, +use the `SliverGrid` widget. -The `SliverList` and `SliverGrid` Widgets take one required parameter: a +The `SliverList` and `SliverGrid` widgets take one required parameter: a [`SliverChildDelegate`]({{site.api}}/flutter/widgets/SliverChildDelegate-class.html). -While this sounds fancy, the delegate is simply used to provide a list Widgets -to `SliverList` or `SliverGrid`. For example, the +While this sounds fancy, the delegate is simply used to provide +a list widgets to `SliverList` or `SliverGrid`. For example, the [`SliverChildBuilderDelegate`]({{site.api}}/flutter/widgets/SliverChildBuilderDelegate-class.html) allows you to create a list of items that are built lazily as you scroll, just -like the the `ListView.builder` Widget. +like the the `ListView.builder` widget. ```dart -// Create a SliverList +// Create a SliverList. SliverList( - // That uses a delegate to build items as they're scrolled on screen. + // Use a delegate to build items as they're scrolled on screen. delegate: SliverChildBuilderDelegate( // The builder function returns a ListTile with a title that - // displays the index of the current item + // displays the index of the current item. (context, index) => ListTile(title: Text('Item #$index')), // Builds 1000 ListTiles childCount: 1000, @@ -158,19 +161,19 @@ class MyApp extends StatelessWidget { title: title, home: Scaffold( // No appbar provided to the Scaffold, only a body with a - // CustomScrollView + // CustomScrollView. body: CustomScrollView( slivers: [ - // Add the app bar to the CustomScrollView + // Add the app bar to the CustomScrollView. SliverAppBar( - // Provide a standard title + // Provide a standard title. title: Text(title), // Allows the user to reveal the app bar if they begin scrolling - // back up the list of items + // back up the list of items. floating: true, - // Display a placeholder Widget to visualize the shrinking size + // Display a placeholder widget to visualize the shrinking size. flexibleSpace: Placeholder(), - // Make the initial height of the SliverAppBar larger than normal + // Make the initial height of the SliverAppBar larger than normal. expandedHeight: 200, ), // Next, create a SliverList @@ -178,7 +181,7 @@ class MyApp extends StatelessWidget { // Use a delegate to build items as they're scrolled on screen. delegate: SliverChildBuilderDelegate( // The builder function returns a ListTile with a title that - // displays the index of the current item + // displays the index of the current item. (context, index) => ListTile(title: Text('Item #$index')), // Builds 1000 ListTiles childCount: 1000, diff --git a/src/docs/cookbook/lists/grid-lists.md b/src/docs/cookbook/lists/grid-lists.md index bb4a7d36d56..b928d87bfdc 100644 --- a/src/docs/cookbook/lists/grid-lists.md +++ b/src/docs/cookbook/lists/grid-lists.md @@ -1,5 +1,5 @@ --- -title: Creating a Grid List +title: Creating a grid list prev: title: Create a horizontal list path: /docs/cookbook/lists/horizontal-list @@ -8,24 +8,26 @@ next: path: /docs/cookbook/lists/mixed-list --- -In some cases, you might want to display your items as a Grid rather than -a normal list of items that come one after the next. For this task, we'll employ -the [`GridView`]({{site.api}}/flutter/widgets/GridView-class.html) Widget. +In some cases, you might want to display your items as a grid rather than +a normal list of items that come one after the next. +For this task, use +the [`GridView`]({{site.api}}/flutter/widgets/GridView-class.html) widget. The simplest way to get started using grids is by using the -[`GridView.count`]({{site.api}}/flutter/widgets/GridView/GridView.count.html) -constructor, because it allow us to specify how many rows or columns we'd like. +[`GridView.count()`]({{site.api}}/flutter/widgets/GridView/GridView.count.html) +constructor, +because it allows you to specify how many rows or columns you'd like. -In this example, we'll generate a List of 100 Widgets that display their -index in the list. This will help us visualize how `GridView` works. +In this example, generate a list of 100 widgets that display their +index in the list. This helps visualize how `GridView` works. ```dart GridView.count( // Create a grid with 2 columns. If you change the scrollDirection to - // horizontal, this would produce 2 rows. + // horizontal, this produces 2 rows. crossAxisCount: 2, - // Generate 100 Widgets that display their index in the List + // Generate 100 widgets that display their index in the List. children: List.generate(100, (index) { return Center( child: Text( @@ -59,9 +61,9 @@ class MyApp extends StatelessWidget { ), body: GridView.count( // Create a grid with 2 columns. If you change the scrollDirection to - // horizontal, this would produce 2 rows. + // horizontal, this produces 2 rows. crossAxisCount: 2, - // Generate 100 Widgets that display their index in the List + // Generate 100 widgets that display their index in the List. children: List.generate(100, (index) { return Center( child: Text( diff --git a/src/docs/cookbook/lists/horizontal-list.md b/src/docs/cookbook/lists/horizontal-list.md index 0e98dbebd68..7173ef546fe 100644 --- a/src/docs/cookbook/lists/horizontal-list.md +++ b/src/docs/cookbook/lists/horizontal-list.md @@ -1,19 +1,19 @@ --- title: Create a horizontal list prev: - title: Basic List + title: Basic list path: /docs/cookbook/lists/basic-list next: - title: Creating a Grid List + title: Creating a grid list path: /docs/cookbook/lists/grid-lists --- -At times, you may want to create a List that scrolls horizontally rather than +You might want to create a list that scrolls horizontally rather than vertically. The [`ListView`]({{site.api}}/flutter/widgets/ListView-class.html) -Widget supports horizontal lists out of the box. +widget supports horizontal lists out of the box. -We'll use the standard `ListView` constructor, passing through a horizontal -`scrollDirection`, which will override the default vertical direction. +Use the standard `ListView` constructor, passing in a horizontal +`scrollDirection`, which overrides the default vertical direction. ```dart diff --git a/src/docs/cookbook/lists/long-lists.md b/src/docs/cookbook/lists/long-lists.md index 4535262098b..2e6e7cfbf98 100644 --- a/src/docs/cookbook/lists/long-lists.md +++ b/src/docs/cookbook/lists/long-lists.md @@ -9,22 +9,22 @@ next: --- The standard [`ListView`]({{site.api}}/flutter/widgets/ListView-class.html) -constructor works well for small lists. In order to work with lists that contain +constructor works well for small lists. To work with lists that contain a large number of items, it's best to use the [`ListView.builder`]({{site.api}}/flutter/widgets/ListView/ListView.builder.html) constructor. -Whereas the default `ListView` constructor requires us to create all items at -once, the `ListView.builder` constructor will create items as they are scrolled +Where the default `ListView` constructor requires creation of all items at +once, the `ListView.builder` constructor creates items as they are scrolled onto the screen. ## 1. Create a data source -First, we'll need a data source to work with. For example, your data source -might be a list of messages, search results, or products in a store. Most of -the time, this data will come from the internet or a database. +First, you need a data source. For example, your data source +might be a list of messages, search results, or products in a store. +Most of the time, this data comes from the internet or a database. -For this example, we'll generate a list of 10000 Strings using the +For this example, generate a list of 10000 Strings using the [`List.generate`]({{site.api}}/flutter/dart-core/List/List.generate.html) constructor. @@ -33,13 +33,11 @@ constructor. final items = List.generate(10000, (i) => "Item $i"); ``` -## 2. Convert the data source into Widgets +## 2. Convert the data source into widgets -In order to display our List of Strings, we'll need to render each String as -a Widget! - -This is where the `ListView.builder` will come into play. In our case, we'll -display each String on its own line. +To display the list of strings, render each String as a widget +using `ListView.builder()`. +In this example, display each String on its own line. ```dart diff --git a/src/docs/cookbook/lists/mixed-list.md b/src/docs/cookbook/lists/mixed-list.md index 7583ec2845c..37306fc86c5 100644 --- a/src/docs/cookbook/lists/mixed-list.md +++ b/src/docs/cookbook/lists/mixed-list.md @@ -1,48 +1,49 @@ --- title: Creating lists with different types of items prev: - title: Creating a Grid List + title: Creating a grid list path: /docs/cookbook/lists/grid-lists next: title: Place a floating app bar above a list path: /docs/cookbook/lists/floating-app-bar --- -We often need to create lists that display different types of content. For -example, we might be working on a List that shows a heading followed by a few -items related to the heading, followed by another heading, and so on. +You might need to create lists that display different types of content. +For example, you might be working on a list that shows a heading +followed by a few items related to the heading, followed by another heading, +and so on. -How would we create such a structure with Flutter? +How to create such a structure with Flutter? ## Directions 1. Create a data source with different types of items - 2. Convert the data source into a List of Widgets + 2. Convert the data source into a list of widgets ## 1. Create a data source with different types of item ### Types of Items -In order to represent different types of items in a List, we'll need to define +To represent different types of items in a list, define a class for each type of item. -In this example, we'll work on an app that shows a header followed by five -messages. Therefore, we'll create three classes: `ListItem`, `HeadingItem`, +In this example, create an app that shows a header followed by five +messages. Therefore, create three classes: `ListItem`, `HeadingItem`, and `MessageItem`. ```dart -// The base class for the different types of items the List can contain +// The base class for the different types of items the List can contain. abstract class ListItem {} -// A ListItem that contains data to display a heading +// A ListItem that contains data to display a heading. class HeadingItem implements ListItem { final String heading; HeadingItem(this.heading); } -// A ListItem that contains data to display a message +// A ListItem that contains data to display a message. class MessageItem implements ListItem { final String sender; final String body; @@ -51,13 +52,13 @@ class MessageItem implements ListItem { } ``` -### Create a List of Items +### Create a list of items -Most of the time, we'd fetch data from the internet or a local database and -convert that data into a list of items. +Most of the time, you would fetch data from the internet or a local +database and convert that data into a list of items. -For this example, we'll generate a list of items to work with. The list will -contain a header followed by five messages. Rinse, repeat. +For this example, generate a list of items to work with. The list +contains a header followed by five messages. And so on. ```dart @@ -69,28 +70,28 @@ final items = List.generate( ); ``` -## 2. Convert the data source into a List of Widgets +## 2. Convert the data source into a list of widgets -In order to handle converting each item into a Widget, we'll employ the -[`ListView.builder`]({{site.api}}/flutter/widgets/ListView/ListView.builder.html) +To convert each item into a widget, use the +[`ListView.builder()`]({{site.api}}/flutter/widgets/ListView/ListView.builder.html) constructor. -In general, we'll want to provide a `builder` function that checks for what type -of item we're dealing with, and returns the appropriate Widget for that type of -item. +In general, provide a `builder` function that checks for what type +of item you're dealing with, and returns the appropriate widget +for that type of item. -In this example, using the `is` keyword to check the type of item we're dealing -with can be handy. It's fast, and will automatically cast each item to the -appropriate type. However, there are different ways to approach this problem if -you prefer another pattern! +This example uses the `is` keyword to check the type of item. +It's fast, and automatically casts each item to the appropriate type. +However, there are different ways to approach this problem if +you prefer another pattern. ```dart ListView.builder( // Let the ListView know how many items it needs to build itemCount: items.length, - // Provide a builder function. This is where the magic happens! We'll - // convert each item into a Widget based on the type of item it is. + // Provide a builder function. This is where the magic happens. + // Convert each item into a widget based on the type of item it is. itemBuilder: (context, index) { final item = items[index]; @@ -144,10 +145,10 @@ class MyApp extends StatelessWidget { title: Text(title), ), body: ListView.builder( - // Let the ListView know how many items it needs to build + // Let the ListView know how many items it needs to build. itemCount: items.length, - // Provide a builder function. This is where the magic happens! We'll - // convert each item into a Widget based on the type of item it is. + // Provide a builder function. This is where the magic happens. + // Convert each item into a widget based on the type of item it is. itemBuilder: (context, index) { final item = items[index]; @@ -171,17 +172,17 @@ class MyApp extends StatelessWidget { } } -// The base class for the different types of items the List can contain +// The base class for the different types of items the List can contain. abstract class ListItem {} -// A ListItem that contains data to display a heading +// A ListItem that contains data to display a heading. class HeadingItem implements ListItem { final String heading; HeadingItem(this.heading); } -// A ListItem that contains data to display a message +// A ListItem that contains data to display a message. class MessageItem implements ListItem { final String sender; final String body; diff --git a/src/docs/cookbook/maintenance/error-reporting.md b/src/docs/cookbook/maintenance/error-reporting.md index 8a6790ded6b..7583ece4db8 100644 --- a/src/docs/cookbook/maintenance/error-reporting.md +++ b/src/docs/cookbook/maintenance/error-reporting.md @@ -4,7 +4,7 @@ prev: title: Working with long lists path: /docs/cookbook/lists/long-lists next: - title: Animating a Widget across screens + title: Animating a widget across screens path: /docs/cookbook/navigation/hero-animations --- @@ -23,7 +23,7 @@ The error tracking service aggregates all of the crashes your users experience and groups them together. This allows you to know how often your app fails and where the users run into trouble. -In this recipe, you'll see how to report errors to the +In this recipe, learn how to report errors to the [Sentry](https://sentry.io/welcome/) crash reporting service. ## Directions @@ -37,7 +37,7 @@ In this recipe, you'll see how to report errors to the ## 1. Get a DSN from Sentry -Before reporting errors to Sentry, you'll need a "DSN" to uniquely identify +Before reporting errors to Sentry, you need a "DSN" to uniquely identify your app with the Sentry.io service. To get a DSN, use the following steps: @@ -61,7 +61,7 @@ dependencies: ## 3. Create a `SentryClient` -Create a `SentryClient`. You'll use the `SentryClient` to send +Create a `SentryClient`. Use the `SentryClient` to send error reports to the sentry service. @@ -78,7 +78,7 @@ lets you know whether you're in debug or production mode. ```dart bool get isInDebugMode { - // Assume you're in production mode + // Assume you're in production mode. bool inDebugMode = false; // Assert expressions are only evaluated during development. They are ignored @@ -96,14 +96,14 @@ errors when the app is in production mode. ```dart Future _reportError(dynamic error, dynamic stackTrace) async { - // Print the exception to the console + // Print the exception to the console. print('Caught error: $error'); if (isInDebugMode) { - // Print the full stacktrace in debug mode + // Print the full stacktrace in debug mode. print(stackTrace); return; } else { - // Send the Exception and Stacktrace to Sentry in Production mode + // Send the Exception and Stacktrace to Sentry in Production mode. _sentry.captureException( exception: error, stackTrace: stackTrace, @@ -120,11 +120,11 @@ you need a way to capture Dart errors. For this task, run your app inside a custom [`Zone`]({{site.api}}/flutter/dart-async/Zone-class.html). Zones establish an execution context for the code. This provides a convenient way to -capture all errors that occur within that context by providing an `onError` +capture all errors that occur within that context by providing an `onError()` function. -In this case, you'll run the app in a new `Zone` and capture all errors by -providing an `onError` callback. +In this case, run the app in a new `Zone` and capture all errors by +providing an `onError()` callback. ```dart @@ -140,7 +140,7 @@ runZoned>(() async { ## 6. Catch and report Flutter errors In addition to Dart errors, Flutter can throw additional errors, such as -platform exceptions that occur when calling native code. You need to be sure to +platform exceptions that occur when calling native code. Be sure to capture and report these types of errors as well. To capture Flutter errors, override the diff --git a/src/docs/cookbook/navigation/hero-animations.md b/src/docs/cookbook/navigation/hero-animations.md index ee694345248..4ff8b931718 100644 --- a/src/docs/cookbook/navigation/hero-animations.md +++ b/src/docs/cookbook/navigation/hero-animations.md @@ -1,5 +1,5 @@ --- -title: Animating a Widget across screens +title: Animating a widget across screens prev: title: Report errors to a service path: /docs/cookbook/maintenance/error-reporting @@ -8,30 +8,34 @@ next: path: /docs/cookbook/navigation/navigation-basics --- -It's often helpful to guide users through our apps as they navigate from screen +It's often helpful to guide users through an app as they navigate from screen to screen. A common technique to lead users through an app is to animate a -Widget from one screen to the next. This creates a visual anchor connecting +widget from one screen to the next. This creates a visual anchor connecting the two screens. -How can we animate a Widget from one screen to the next with Flutter? Using the -[`Hero`]({{site.api}}/flutter/widgets/Hero-class.html) Widget! +How to animate a widget from one screen to the next with Flutter? +By using the +[`Hero`]({{site.api}}/flutter/widgets/Hero-class.html) widget. ## Directions 1. Create two screens showing the same image - 2. Add a `Hero` Widget to the first screen - 3. Add a `Hero` Widget to the second screen + 2. Add a `Hero` widget to the first screen + 3. Add a `Hero` widget to the second screen ## 1. Create two screens showing the same image -In this example, we'll display the same image on both screens. We'll want to -animate the image from the first screen to the second screen when the user taps -on the image. For now, we'll create the visual structure, and handle animations -in the next steps! +In this example, display the same image on both screens. +Animate the image from the first screen to the second screen when the user taps +the image. For now, create the visual structure, and handle animations +in the next steps. -*Note:* This example builds upon the -[Navigate to a new screen and back](/docs/cookbook/navigation/navigation-basics) -and [Handling Taps](/docs/cookbook/gestures/handling-taps) recipes. +{{site.alert.note}} + This example builds upon the + [Navigate to a new screen and + back](/docs/cookbook/navigation/navigation-basics) + and [Handling taps](/docs/cookbook/gestures/handling-taps) recipes. +{{site.alert.end}} ```dart class MainScreen extends StatelessWidget { @@ -74,15 +78,15 @@ class DetailScreen extends StatelessWidget { } ``` -## 2. Add a `Hero` Widget to the first screen +## 2. Add a `Hero` widget to the first screen -In order to connect the two screens together with an animation, we need to wrap -the `Image` Widget on both screens in a `Hero` Widget. The `Hero` Widget +To connect the two screens together with an animation, wrap +the `Image` widget on both screens in a `Hero` widget. The `Hero` widget requires two arguments: 1. `tag`: An object that identifies the `Hero`. It must be the same on both screens. - 2. `child`: The Widget we want to animate across screens. + 2. `child`: The widget to animate across screens. ```dart @@ -94,14 +98,14 @@ Hero( ); ``` -## 3. Add a `Hero` Widget to the second screen +## 3. Add a `Hero` widget to the second screen -To complete the connection with the first screen, we need to wrap the `Image` -on the second screen with a `Hero` Widget as well! It must use the same `tag` -as the first screen. +To complete the connection with the first screen, wrap the `Image` +on the second screen with a `Hero` widget as well. +It must use the same `tag` as the first screen. -After you apply the `Hero` Widget to the second screen, the animation between -screens will work! +After applying the `Hero` widget to the second screen, +the animation between screens just works. ```dart @@ -113,9 +117,12 @@ Hero( ); ``` -Note: this code is identical to what we had on the first screen! In general, you -could create a reusable Widget instead of repeating code, but for this example, -we'll duplicate the code for demonstration purposes. +{{site.alert.note}} + This code is identical to what you have on the first screen. + As a best practice, you would create a reusable widget instead of + repeating code, but for purposes of demonstration, the example + duplicates the code. +{{site.alert.end}} ## Complete example diff --git a/src/docs/cookbook/navigation/named-routes.md b/src/docs/cookbook/navigation/named-routes.md index 08a82f830f5..417350a14e8 100644 --- a/src/docs/cookbook/navigation/named-routes.md +++ b/src/docs/cookbook/navigation/named-routes.md @@ -9,18 +9,19 @@ next: --- In the -[Navigate to a new screen and back](/docs/cookbook/navigation/navigation-basics/) -recipe, we learned how to Navigate to a new screen by creating a new route and +[Navigate to a new screen and +back](/docs/cookbook/navigation/navigation-basics/) recipe, +you learned how to Navigate to a new screen by creating a new route and pushing it to the [`Navigator`]({{site.api}}/flutter/widgets/Navigator-class.html). -However, if we need to navigate to the same screen in many parts of our apps, -this can result in code duplication. In these cases, it can be handy to define -a "named route," and use the named route for Navigation. +However, if you need to navigate to the same screen in many parts of your app, +this can result in code duplication. In these cases, define +a "named route," and use the named route for navigation. -To work with named routes, we can use the -[`Navigator.pushNamed`]({{site.api}}/flutter/widgets/Navigator/pushNamed.html) -function. This example will replicate the functionality from the original +To work with named routes, use the +[`Navigator.pushNamed()`]({{site.api}}/flutter/widgets/Navigator/pushNamed.html) +function. This example replicates the functionality from the original recipe, demonstrating how to use named routes instead. ## Directions @@ -32,8 +33,8 @@ recipe, demonstrating how to use named routes instead. ## 1. Create two screens -First, we'll need two screens to work with. The first screen will contain a -button that navigates to the second screen. The second screen will contain a +First, create two screens to work with. The first screen contains a +button that navigates to the second screen. The second screen contains a button that navigates back to the first. ```dart @@ -48,7 +49,7 @@ class FirstScreen extends StatelessWidget { child: RaisedButton( child: Text('Launch screen'), onPressed: () { - // Navigate to second screen when tapped! + // Navigate to second screen when tapped. }, ), ), @@ -66,7 +67,7 @@ class SecondScreen extends StatelessWidget { body: Center( child: RaisedButton( onPressed: () { - // Navigate back to first screen when tapped! + // Navigate back to first screen when tapped. }, child: Text('Go back!'), ), @@ -78,63 +79,65 @@ class SecondScreen extends StatelessWidget { ## 2. Define the routes -Next, we'll need to define our routes by providing additional properties to the +Next, define the routes by providing additional properties to the [`MaterialApp`]({{site.api}}/flutter/material/MaterialApp-class.html) constructor: the `initialRoute` and the `routes` themselves. -The `initialRoute` property defines which route our app should start with. The -`routes` property defines the available named routes and the Widgets that should -be built when we navigate to those routes. +The `initialRoute` property defines which route the app should start with. +The `routes` property defines the available named routes and the widgets +that should be built when navigating to those routes. ```dart MaterialApp( - // Start the app with the "/" named route. In our case, the app will start - // on the FirstScreen Widget + // Start the app with the "/" named route. In this case, the app starts + // on the FirstScreen widget. initialRoute: '/', routes: { - // When we navigate to the "/" route, build the FirstScreen Widget + // When navigating to the "/" route, build the FirstScreen widget. '/': (context) => FirstScreen(), - // When we navigate to the "/second" route, build the SecondScreen Widget + // When navigating to the "/second" route, build the SecondScreen widget. '/second': (context) => SecondScreen(), }, ); ``` -Note: When using `initialRoute`, be sure you do not define a `home` property. +{{site.alert.note}} + When using `initialRoute`, be sure not to define a `home` property. +{{site.alert.end}} ## 3. Navigate to the second screen -With our Widgets and routes in place, we can begin navigating! In this case, -we'll use the -[`Navigator.pushNamed`]({{site.api}}/flutter/widgets/Navigator/pushNamed.html) -function. This tells Flutter to build the Widget defined in our `routes` table -and launch the screen. +With the widgets and routes in place, begin navigating. In this case, +use the +[`Navigator.pushNamed()`]({{site.api}}/flutter/widgets/Navigator/pushNamed.html) +function. This tells Flutter to build the widget defined in the +`routes` table and launch the screen. -In the `build` method of our `FirstScreen` Widget, we'll update the `onPressed` +In the `build()` method of the `FirstScreen` widget, update the `onPressed()` callback: ```dart -// Within the `FirstScreen` Widget +// Within the `FirstScreen` widget onPressed: () { - // Navigate to the second screen using a named route + // Navigate to the second screen using a named route. Navigator.pushNamed(context, '/second'); } ``` ## 4. Return to the first screen -In order to navigate back to the first page, we can use the -[`Navigator.pop`]({{site.api}}/flutter/widgets/Navigator/pop.html) +To navigate back to the first screen, use the +[`Navigator.pop()`]({{site.api}}/flutter/widgets/Navigator/pop.html) function. ```dart -// Within the SecondScreen Widget +// Within the SecondScreen widget onPressed: () { // Navigate back to the first screen by popping the current route - // off the stack + // off the stack. Navigator.pop(context); } ``` @@ -147,13 +150,13 @@ import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( title: 'Named Routes Demo', - // Start the app with the "/" named route. In our case, the app will start - // on the FirstScreen Widget + // Start the app with the "/" named route. In this case, the app starts + // on the FirstScreen widget. initialRoute: '/', routes: { - // When we navigate to the "/" route, build the FirstScreen Widget + // When navigating to the "/" route, build the FirstScreen widget. '/': (context) => FirstScreen(), - // When we navigate to the "/second" route, build the SecondScreen Widget + // When navigating to the "/second" route, build the SecondScreen widget. '/second': (context) => SecondScreen(), }, )); @@ -170,7 +173,7 @@ class FirstScreen extends StatelessWidget { child: RaisedButton( child: Text('Launch screen'), onPressed: () { - // Navigate to the second screen using a named route + // Navigate to the second screen using a named route. Navigator.pushNamed(context, '/second'); }, ), @@ -190,7 +193,7 @@ class SecondScreen extends StatelessWidget { child: RaisedButton( onPressed: () { // Navigate back to the first screen by popping the current route - // off the stack + // off the stack. Navigator.pop(context); }, child: Text('Go back!'), diff --git a/src/docs/cookbook/navigation/navigate-with-arguments.md b/src/docs/cookbook/navigation/navigate-with-arguments.md index 5a13d9c5f94..e7600394394 100644 --- a/src/docs/cookbook/navigation/navigate-with-arguments.md +++ b/src/docs/cookbook/navigation/navigate-with-arguments.md @@ -10,17 +10,17 @@ next: The [`Navigator`]({{site.api}}/flutter/widgets/Navigator-class.html) provides the ability to navigate to a named route from any part of an app using -a common identifier. In some cases, you may also need to pass arguments to a -named route. For example, you may wish to navigate to the `/user` route and +a common identifier. In some cases, you might also need to pass arguments to a +named route. For example, you might wish to navigate to the `/user` route and pass information about the user to that route. -In Flutter, you can accomplish this task by providing additional `arguments` to -the -[`Navigator.pushNamed`]({{site.api}}/flutter/widgets/Navigator/pushNamed.html) -method. You can extract the arguments using the +In Flutter, +you can accomplish this task by providing additional `arguments` to the +[`Navigator.pushNamed()`]({{site.api}}/flutter/widgets/Navigator/pushNamed.html) +method. Extract the arguments using the [`ModalRoute.of`]({{site.api}}/flutter/widgets/ModalRoute/of.html) method or inside an -[`onGenerateRoute`]({{site.api}}/flutter/widgets/WidgetsApp/onGenerateRoute.html) +[`onGenerateRoute()`]({{site.api}}/flutter/widgets/WidgetsApp/onGenerateRoute.html) function provided to the [`MaterialApp`]({{site.api}}/flutter/material/MaterialApp-class.html) or @@ -28,7 +28,7 @@ or constructor. This recipe demonstrates how to pass arguments to a named route and read the -arguments using `ModelRoute.of` and `onGenerateRoute`. +arguments using `ModelRoute.of()` and `onGenerateRoute()`. ## Directions @@ -39,15 +39,17 @@ arguments using `ModelRoute.of` and `onGenerateRoute`. ## 1. Define the arguments you need to pass -First, define the arguments you need to pass to the new route. In this example, -pass two pieces of data: The `title` of the screen and a `message`. +First, define the arguments you need to pass to the new route. +In this example, pass two pieces of data: +The `title` of the screen and a `message`. To pass both pieces of data, create a class that stores this information. ```dart -// You can pass any object to the arguments parameter. In this example, create a -// class that contains both a customizable title and message. +// You can pass any object to the arguments parameter. +// In this example, create a class that contains both a customizable +// title and message. class ScreenArguments { final String title; final String message; @@ -60,12 +62,12 @@ class ScreenArguments { Next, create a widget that extracts and displays the `title` and `message` from the `ScreenArguments`. To access the `ScreenArguments`, use the -[`ModalRoute.of`]({{site.api}}/flutter/widgets/ModalRoute/of.html) +[`ModalRoute.of()`]({{site.api}}/flutter/widgets/ModalRoute/of.html) method. This method returns the current route with the arguments. ```dart -// A Widget that extracts the necessary arguments from the ModalRoute. +// A widget that extracts the necessary arguments from the ModalRoute. class ExtractArgumentsScreen extends StatelessWidget { static const routeName = '/extractArguments'; @@ -89,7 +91,7 @@ class ExtractArgumentsScreen extends StatelessWidget { ## 3. Register the widget in the `routes` table -Next, add an entry to the `routes` provided to the `MaterialApp` Widget. The +Next, add an entry to the `routes` provided to the `MaterialApp` widget. The `routes` define which widget should be created based on the name of the route. @@ -106,19 +108,19 @@ MaterialApp( Finally, navigate to the `ExtractArgumentsScreen` when a user taps a button using -[`Navigator.pushNamed`]({{site.api}}/flutter/widgets/Navigator/pushNamed.html). +[`Navigator.pushNamed()`]({{site.api}}/flutter/widgets/Navigator/pushNamed.html). Provide the arguments to the route via the `arguments` property. The `ExtractArgumentsScreen` extracts the `title` and `message` from these arguments. ```dart -// A button that navigates to a named route that. The named route +// A button that navigates to a named route. The named route // extracts the arguments by itself. RaisedButton( child: Text("Navigate to screen that extracts arguments"), onPressed: () { - // When the user taps the button, navigate to the specific rout + // When the user taps the button, navigate to the specific route // and provide the arguments as part of the RouteSettings. Navigator.pushNamed( context, @@ -136,17 +138,17 @@ RaisedButton( Instead of extracting the arguments directly inside the widget, you can also extract the arguments inside an -[`onGenerateRoute`]({{site.api}}/flutter/widgets/WidgetsApp/onGenerateRoute.html) +[`onGenerateRoute()`]({{site.api}}/flutter/widgets/WidgetsApp/onGenerateRoute.html) function and pass them to a widget. -The `onGenerateRoute` function creates the correct route based on the given +The `onGenerateRoute()` function creates the correct route based on the given `RouteSettings`. ```dart MaterialApp( // Provide a function to handle named routes. Use this function to - // identify the named route being pushed and create the correct + // identify the named route being pushed, and create the correct // Screen. onGenerateRoute: (settings) { // If you push the PassArguments route @@ -181,7 +183,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( // Provide a function to handle named routes. Use this function to - // identify the named route being pushed and create the correct + // identify the named route being pushed, and create the correct // Screen. onGenerateRoute: (settings) { // If you push the PassArguments route @@ -319,8 +321,8 @@ class PassArgumentsScreen extends StatelessWidget { } } -// You can pass any object to the arguments parameter. In this example, create a -// class that contains both a customizable title and message. +// You can pass any object to the arguments parameter. In this example, +// create a class that contains both a customizable title and message. class ScreenArguments { final String title; final String message; diff --git a/src/docs/cookbook/navigation/navigation-basics.md b/src/docs/cookbook/navigation/navigation-basics.md index 4ab8a5dd463..cdfb7e92461 100644 --- a/src/docs/cookbook/navigation/navigation-basics.md +++ b/src/docs/cookbook/navigation/navigation-basics.md @@ -2,18 +2,20 @@ title: Navigate to a new screen and back description: How to navigate between routes prev: - title: Animating a Widget across screens + title: Animating a widget across screens path: /docs/cookbook/navigation/hero-animations next: title: Navigate with named routes path: /docs/cookbook/navigation/named-routes --- -Most apps contain several screens for displaying different types of information. -For example, an app might have a screen that displays products. Users can then -tap the image of a product to get more detailed information on a new screen. +Most apps contain several screens for displaying different types of +information. +For example, an app might have a screen that displays products. +Users can then tap the image of a product to get more detailed +information on a new screen. -{{site.alert.info}} +{{site.alert.secondary}} **Terminology**: In Flutter, _screens_ and _pages_ are called _routes_. The remainder of this doc refers to routes. {{site.alert.end}} @@ -91,11 +93,11 @@ method. The `push()` method adds a `Route` to the stack of routes managed by the Navigator. Where does the `Route` come from? You can create your own, or use a [`MaterialPageRoute`]({{site.api}}/flutter/material/MaterialPageRoute-class.html), -out of the box. `MaterialPageRoute` is handy because it transitions to the +out of the box. `MaterialPageRoute` is useful because it transitions to the new route using a platform-specific animation. -In the `build()` method of the `FirstRoute` widget, update the `onPressed()` -callback: +In the `build()` method of the `FirstRoute` widget, +update the `onPressed()` callback: ```dart diff --git a/src/docs/cookbook/navigation/passing-data.md b/src/docs/cookbook/navigation/passing-data.md index c04d9b03025..733e0ab88e0 100644 --- a/src/docs/cookbook/navigation/passing-data.md +++ b/src/docs/cookbook/navigation/passing-data.md @@ -8,24 +8,26 @@ next: path: /docs/cookbook/networking/fetch-data --- -Oftentimes, we not only want to navigate to a new screen, but also pass some -data to the screen as well. For example, we often want to pass information about -the item we tapped on. - -Remember: Screens are Just Widgets™. In this example, we'll create a List -of Todos. When a todo is tapped on, we'll navigate to a new screen (Widget) that +Often, you not only want to navigate to a new screen, +but also pass data to the screen as well. +For example, you might want to pass information about +the item that's been tapped. + +Remember: Screens are Just Widgets™. +In this example, create a List of Todos. +When a todo is tapped, navigate to a new screen (widget) that displays information about the todo. ## Directions 1. Define a Todo class - 2. Display a List of Todos - 3. Create a Detail Screen that can display information about a todo - 4. Navigate and pass data to the Detail Screen + 2. Display a list of Todos + 3. Create a detail screen that can display information about a todo + 4. Navigate and pass data to the detail screen ## 1. Define a Todo class -First, we'll need a simple way to represent Todos. For this example, we'll +First, you need a simple way to represent Todos. For this example, create a class that contains two pieces of data: the title and description. @@ -38,13 +40,14 @@ class Todo { } ``` -## 2. Create a List of Todos +## 2. Create a list of Todos -Second, we'll want to display a list of Todos. In this example, we'll generate -20 todos and show them using a ListView. For more information on working with -Lists, please see the [`Basic List`](/docs/cookbook/lists/basic-list/) recipe. +Second, display a list of Todos. In this example, generate +20 todos and show them using a ListView. +For more information on working with lists, see the +[Basic List](/docs/cookbook/lists/basic-list/) recipe. -### Generate the List of Todos +### Generate the list of Todos ```dart @@ -57,7 +60,7 @@ final todos = List.generate( ); ``` -### Display the List of Todos using a ListView +### Display the list of Todos using a ListView ```dart @@ -71,28 +74,29 @@ ListView.builder( ); ``` -So far, so good. We'll generate 20 Todos and display them in a ListView! +So far, so good. +This generates 20 Todos and displays them in a ListView. -## 3. Create a Detail Screen that can display information about a todo +## 3. Create a detail screen to display information about a todo -Now, we'll create our second screen. The title of the screen will contain the -title of the todo, and the body of the screen will show the description. +Now, create the second screen. The title of the screen contains the +title of the todo, and the body of the screen shows the description. -Since it's a normal `StatelessWidget`, we'll simply require that users creating -the Screen pass through a `Todo`! Then, we'll build a UI using the given Todo. +Since it's a normal `StatelessWidget`, require that the user +enters a `Todo` in the UI. Then, build the UI using the given Todo. ```dart class DetailScreen extends StatelessWidget { - // Declare a field that holds the Todo + // Declare a field that holds the Todo. final Todo todo; - // In the constructor, require a Todo + // In the constructor, require a Todo. DetailScreen({Key key, @required this.todo}) : super(key: key); @override Widget build(BuildContext context) { - // Use the Todo to create our UI + // Use the Todo to create the UI. return Scaffold( appBar: AppBar( title: Text(todo.title), @@ -106,16 +110,17 @@ class DetailScreen extends StatelessWidget { } ``` -## 4. Navigate and pass data to the Detail Screen +## 4. Navigate and pass data to the detail screen -With our `DetailScreen` in place, we're ready to perform the Navigation! In our -case, we'll want to Navigate to the `DetailScreen` when a user taps on a Todo in -our List. When we do so, we'll also want to pass the Todo to the `DetailScreen`. +With a `DetailScreen` in place, +you're ready to perform the Navigation. +In this example, navigate to the `DetailScreen` when a user taps a Todo in +the list. When doing so, pass the Todo to the `DetailScreen`. -To achieve this, we'll write an -[`onTap`]({{site.api}}/flutter/material/ListTile/onTap.html) -callback for our `ListTile` Widget. Within our `onTap` callback, we'll once -again employ the [`Navigator.push`]({{site.api}}/flutter/widgets/Navigator/push.html) +To achieve this, write an +[`onTap()`]({{site.api}}/flutter/material/ListTile/onTap.html) +callback for the `ListTile` widget. Within the `onTap()` callback, +use the [`Navigator.push()`]({{site.api}}/flutter/widgets/Navigator/push.html) method. @@ -125,9 +130,9 @@ ListView.builder( itemBuilder: (context, index) { return ListTile( title: Text(todos[index].title), - // When a user taps on the ListTile, navigate to the DetailScreen. - // Notice that we're not only creating a DetailScreen, we're - // also passing the current todo to it! + // When a user taps the ListTile, navigate to the DetailScreen. + // Notice that you're not only creating a DetailScreen, you're + // also passing the current todo to it. onTap: () { Navigator.push( context, @@ -185,9 +190,9 @@ class TodosScreen extends StatelessWidget { itemBuilder: (context, index) { return ListTile( title: Text(todos[index].title), - // When a user taps on the ListTile, navigate to the DetailScreen. - // Notice that we're not only creating a DetailScreen, we're - // also passing the current todo through to it! + // When a user taps the ListTile, navigate to the DetailScreen. + // Notice that you're not only creating a DetailScreen, you're + // also passing the current todo through to it. onTap: () { Navigator.push( context, @@ -204,15 +209,15 @@ class TodosScreen extends StatelessWidget { } class DetailScreen extends StatelessWidget { - // Declare a field that holds the Todo + // Declare a field that holds the Todo. final Todo todo; - // In the constructor, require a Todo + // In the constructor, require a Todo. DetailScreen({Key key, @required this.todo}) : super(key: key); @override Widget build(BuildContext context) { - // Use the Todo to create our UI + // Use the Todo to create the UI. return Scaffold( appBar: AppBar( title: Text(todo.title), diff --git a/src/docs/cookbook/navigation/returning-data.md b/src/docs/cookbook/navigation/returning-data.md index ffd6628242f..be433302bb3 100644 --- a/src/docs/cookbook/navigation/returning-data.md +++ b/src/docs/cookbook/navigation/returning-data.md @@ -8,13 +8,13 @@ next: path: /docs/cookbook/navigation/passing-data --- -In some cases, we might want to return data from a new screen. For example, say -we push a new screen that presents two options to a user. When the user taps on -an option, we'll want to inform our first screen of the user's selection so it -can act on that information! +In some cases, you might want to return data from a new screen. +For example, say you push a new screen that presents two options to a user. +When the user taps an option, you want to inform the first screen +of the user's selection so it can act on that information. -How can we achieve this? Using -[`Navigator.pop`]({{site.api}}/flutter/widgets/Navigator/pop.html)! +How to achieve this? By using +[`Navigator.pop()`]({{site.api}}/flutter/widgets/Navigator/pop.html). ## Directions @@ -26,8 +26,8 @@ How can we achieve this? Using ## 1. Define the home screen -The home screen will display a button. When tapped, it will launch the selection -screen! +The home screen displays a button. When tapped, it launches the selection +screen. ```dart @@ -38,7 +38,7 @@ class HomeScreen extends StatelessWidget { appBar: AppBar( title: Text('Returning Data Demo'), ), - // We'll create the SelectionButton Widget in the next step + // Create the SelectionButton widget in the next step. body: Center(child: SelectionButton()), ); } @@ -47,10 +47,10 @@ class HomeScreen extends StatelessWidget { ## 2. Add a button that launches the selection screen -Now, we'll create our SelectionButton. Our selection button will: +Now, create the SelectionButton. The selection button: - 1. Launch the SelectionScreen when it's tapped - 2. Wait for the SelectionScreen to return a result + 1. Launches the SelectionScreen when it's tapped + 2. Waits for the SelectionScreen to return a result ```dart @@ -68,11 +68,11 @@ class SelectionButton extends StatelessWidget { // A method that launches the SelectionScreen and awaits the result from // Navigator.pop _navigateAndDisplaySelection(BuildContext context) async { - // Navigator.push returns a Future that will complete after we call - // Navigator.pop on the Selection Screen! + // Navigator.push returns a Future that completes after calling + // Navigator.pop on the Selection Screen. final result = await Navigator.push( context, - // We'll create the SelectionScreen in the next step! + // Create the SelectionScreen in the next step. MaterialPageRoute(builder: (context) => SelectionScreen()), ); } @@ -81,11 +81,11 @@ class SelectionButton extends StatelessWidget { ## 3. Show the selection screen with two buttons -Now, we'll need to build a selection screen! It will contain two buttons. When -a user taps on a button, it should close the selection screen and let the home -screen know which button was tapped! +Now, build a selection screen. It contains two buttons. When +a user taps a button, it closes the selection screen and lets the home +screen know which button was tapped. -For now, we'll define the UI, and figure out how to return data in the next +For now, define the UI, and figure out how to return data in the next step. ```dart @@ -128,13 +128,13 @@ class SelectionScreen extends StatelessWidget { ## 4. When a button is tapped, close the selection screen -Now, we'll want to update the `onPressed` callback for both of our buttons! In -order to return data to the first screen, we'll need to use the -[`Navigator.pop`]({{site.api}}/flutter/widgets/Navigator/pop.html) +Now, update the `onPressed()` callback for both of our buttons. In +order to return data to the first screen, use the +[`Navigator.pop()`]({{site.api}}/flutter/widgets/Navigator/pop.html) method. -`Navigator.pop` accepts an optional second argument called `result`. If we -provide a result, it will be returned to the `Future` in our SelectionButton! +`Navigator.pop()` accepts an optional second argument called `result`. +If providing a result, it's returned to the `Future` in the SelectionButton. ### Yep button @@ -142,7 +142,7 @@ provide a result, it will be returned to the `Future` in our SelectionButton! ```dart RaisedButton( onPressed: () { - // Our Yep button will return "Yep!" as the result + // The Yep button returns "Yep!" as the result. Navigator.pop(context, 'Yep!'); }, child: Text('Yep!'), @@ -155,7 +155,7 @@ RaisedButton( ```dart RaisedButton( onPressed: () { - // Our Nope button will return "Nope!" as the result + // The Nope button returns "Nope!" as the result. Navigator.pop(context, 'Nope!'); }, child: Text('Nope!'), @@ -164,11 +164,11 @@ RaisedButton( ## 5. Show a snackbar on the home screen with the selection -Now that we're launching a selection screen and awaiting the result, we'll want -to do something with the information that's returned! +Now that you're launching a selection screen and awaiting the result, +you'll want to do something with the information that's returned. -In this case, we'll show a Snackbar displaying the result. To do so, we'll -update the `_navigateAndDisplaySelection` method in our `SelectionButton`. +In this case, show a Snackbar displaying the result. To do so, +update the `_navigateAndDisplaySelection()` method in `SelectionButton`. ```dart @@ -179,7 +179,7 @@ _navigateAndDisplaySelection(BuildContext context) async { ); // After the Selection Screen returns a result, hide any previous snackbars - // and show the new result! + // and show the new result. Scaffold.of(context) ..removeCurrentSnackBar() ..showSnackBar(SnackBar(content: Text("$result"))); @@ -222,17 +222,17 @@ class SelectionButton extends StatelessWidget { } // A method that launches the SelectionScreen and awaits the result from - // Navigator.pop! + // Navigator.pop. _navigateAndDisplaySelection(BuildContext context) async { - // Navigator.push returns a Future that will complete after we call - // Navigator.pop on the Selection Screen! + // Navigator.push returns a Future that completes after calling + // Navigator.pop on the Selection Screen. final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => SelectionScreen()), ); // After the Selection Screen returns a result, hide any previous snackbars - // and show the new result! + // and show the new result. Scaffold.of(context) ..removeCurrentSnackBar() ..showSnackBar(SnackBar(content: Text("$result"))); @@ -254,7 +254,7 @@ class SelectionScreen extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: RaisedButton( onPressed: () { - // Close the screen and return "Yep!" as the result + // Close the screen and return "Yep!" as the result. Navigator.pop(context, 'Yep!'); }, child: Text('Yep!'), @@ -264,7 +264,7 @@ class SelectionScreen extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: RaisedButton( onPressed: () { - // Close the screen and return "Nope!" as the result + // Close the screen and return "Nope!" as the result. Navigator.pop(context, 'Nope.'); }, child: Text('Nope.'), diff --git a/src/docs/cookbook/networking/authenticated-requests.md b/src/docs/cookbook/networking/authenticated-requests.md index 87f9d230250..cb85c8be8e8 100644 --- a/src/docs/cookbook/networking/authenticated-requests.md +++ b/src/docs/cookbook/networking/authenticated-requests.md @@ -8,7 +8,7 @@ next: path: /docs/cookbook/networking/background-parsing --- -In order to fetch data from many web services, you need to provide +To fetch data from many web services, you need to provide authorization. There are many ways to do this, but perhaps the most common uses the `Authorization` HTTP header. @@ -23,7 +23,7 @@ the `dart:io` package for common `HttpHeaders`. Future fetchPost() { return http.get( 'https://jsonplaceholder.typicode.com/posts/1', - // Send authorization headers to the backend + // Send authorization headers to the backend. headers: {HttpHeaders.authorizationHeader: "Basic your_api_token_here"}, ); } @@ -32,7 +32,7 @@ Future fetchPost() { ## Complete example This example builds upon the [Fetching Data from the -Internet](/docs/cookbook/networking/fetch-data/) recipe. +internet](/docs/cookbook/networking/fetch-data/) recipe. ```dart import 'dart:async'; diff --git a/src/docs/cookbook/networking/background-parsing.md b/src/docs/cookbook/networking/background-parsing.md index 0fea7b103b2..dbe344d5c4d 100644 --- a/src/docs/cookbook/networking/background-parsing.md +++ b/src/docs/cookbook/networking/background-parsing.md @@ -12,12 +12,12 @@ By default, Dart apps do all of their work on a single thread. In many cases, this model simplifies coding and is fast enough that it does not result in poor app performance or stuttering animations, often called "jank." -However, you may need to perform an expensive computation, such as parsing a -very large JSON document. If this work takes more than 16 milliseconds, your -users will experience jank. +However, you might need to perform an expensive computation, such as parsing a +very large JSON document. If this work takes more than 16 milliseconds, +your users experience jank. To avoid jank, you need to perform expensive computations like this in the -background. On Android, this would mean scheduling work on a different thread. +background. On Android, this means scheduling work on a different thread. In Flutter, you can use a separate [Isolate]({{site.api}}/flutter/dart-isolate/Isolate-class.html). @@ -25,7 +25,7 @@ In Flutter, you can use a separate 1. Add the `http` package 2. Make a network request using the `http` package - 3. Convert the response into a List of Photos + 3. Convert the response into a list of Photos 4. Move this work to a separate isolate ## 1. Add the `http` package @@ -41,7 +41,7 @@ dependencies: ## 2. Make a network request -In this example, you'll fetch a JSON large document that contains a list of +In this example, fetch a JSON large document that contains a list of 5000 photo objects from the [JSONPlaceholder REST API](https://jsonplaceholder.typicode.com) using the [http.get()]({{site.pub-api}}/http/latest/http/get.html) method. @@ -53,20 +53,22 @@ Future fetchPhotos(http.Client client) async { } ``` -Note: You're providing an `http.Client` to the function in this example. -This makes the function easier to test and use in different environments. +{{site.alert.note}} + You're providing an `http.Client` to the function in this example. + This makes the function easier to test and use in different environments. +{{site.alert.end}} -## 3. Parse and Convert the json into a List of Photos +## 3. Parse and Convert the json into a list of Photos Next, following the guidance from the [Fetch data from the internet](/docs/cookbook/networking/fetch-data) -recipe, you'll want to convert the `http.Response` into a list of Dart objects. +recipe, convert the `http.Response` into a list of Dart objects. This makes the data easier to work with in the future. ### Create a `Photo` class First, create a `Photo` class that contains data about a photo. -You will include a `fromJson` factory method to make it easy to create a +Include a `fromJson()` factory method to make it easy to create a `Photo` starting with a json object. @@ -88,17 +90,18 @@ class Photo { } ``` -### Convert the response into a List of Photos +### Convert the response into a list of Photos -Now, update the `fetchPhotos` function so it can return a -`Future>`. To do so, you'll need to: +Now, update the `fetchPhotos()` function so it returns a +`Future>`. To do so, you need to: - 1. Create a `parsePhotos` that converts the response body into a `List` + 1. Create a `parsePhotos` that converts the response body into a + `List` 2. Use the `parsePhotos` function in the `fetchPhotos` function ```dart -// A function that converts a response body into a List +// A function that converts a response body into a List. List parsePhotos(String responseBody) { final parsed = json.decode(responseBody).cast>(); @@ -115,15 +118,15 @@ Future> fetchPhotos(http.Client client) async { ## 4. Move this work to a separate isolate -If you run the `fetchPhotos` function on a slower phone, you may notice the app -freezes for a brief moment as it parses and converts the json. This is jank, -and we want to be rid of it. +If you run the `fetchPhotos()` function on a slower device, +you might notice the app freezes for a brief moment as it parses and +converts the json. This is jank, and you want to be rid of it. -So how can we do that? By moving the parsing and conversion to a background -isolate using the [`compute`]({{site.api}}/flutter/foundation/compute.html) -function provided by Flutter. The `compute` function runs expensive +So how to do that? By moving the parsing and conversion to a background +isolate using the [`compute()`]({{site.api}}/flutter/foundation/compute.html) +function provided by Flutter. The `compute()` function runs expensive functions in a background isolate and returns the result. In this case, -we want to run the `parsePhotos` function in the background. +run the `parsePhotos` function in the background. ```dart @@ -131,7 +134,7 @@ Future> fetchPhotos(http.Client client) async { final response = await client.get('https://jsonplaceholder.typicode.com/photos'); - // Use the compute function to run parsePhotos in a separate isolate + // Use the compute function to run parsePhotos in a separate isolate. return compute(parsePhotos, response.body); } ``` @@ -142,7 +145,7 @@ Isolates communicate by passing messages back and forth. These messages can be primitive values, such as `null`, `num`, `bool`, `double`, or `String`, or simple objects such as the `List` in this example. -You may experience errors if you try to pass more complex objects, such as +You might experience errors if you try to pass more complex objects, such as a `Future` or `http.Response` between isolates. ## Complete example @@ -159,11 +162,11 @@ Future> fetchPhotos(http.Client client) async { final response = await client.get('https://jsonplaceholder.typicode.com/photos'); - // Use the compute function to run parsePhotos in a separate isolate + // Use the compute function to run parsePhotos in a separate isolate. return compute(parsePhotos, response.body); } -// A function that converts a response body into a List +// A function that converts a response body into a List. List parsePhotos(String responseBody) { final parsed = json.decode(responseBody).cast>(); diff --git a/src/docs/cookbook/networking/fetch-data.md b/src/docs/cookbook/networking/fetch-data.md index 57565d33018..7830652692f 100644 --- a/src/docs/cookbook/networking/fetch-data.md +++ b/src/docs/cookbook/networking/fetch-data.md @@ -8,8 +8,8 @@ next: path: /docs/cookbook/networking/authenticated-requests --- -Fetching data from the internet is necessary for most apps. Luckily, Dart and -Flutter provide tools for this type of work. +Fetching data from the internet is necessary for most apps. +Luckily, Dart and Flutter provide tools for this type of work. ## Directions @@ -23,7 +23,7 @@ Flutter provide tools for this type of work. The [`http`]({{site.pub-pkg}}/http) package provides the simplest way to fetch data from the internet. -To install the `http` package, you must add it to the dependencies section +To install the `http` package, add it to the dependencies section of the `pubspec.yaml`. You can [find the latest version of the http package on the Pub site]({{site.pub}}/packages/http#-installing-tab-). @@ -34,7 +34,7 @@ dependencies: ## 2. Make a network request -In this example, you'll fetch a sample post from the +In this example, fetch a sample post from the [JSONPlaceholder REST API](https://jsonplaceholder.typicode.com/) using the [http.get()]({{site.pub-api}}/http/latest/http/get.html) method. @@ -49,8 +49,8 @@ The `http.get()` method returns a `Future` that contains a `Response`. * [`Future`]({{site.api}}/flutter/dart-async/Future-class.html) is a core Dart class for working with async operations. - It is used to represent a potential value or error that will - be available at some time in the future. + It is used to represent a potential value or error that is + available at some time in the future. * The `http.Response` class contains the data received from a successful http call. @@ -63,11 +63,11 @@ convert the `http.Response` into a Dart object. ### Create a `Post` class First, create a `Post` class that contains the data from the -network request. It will include a factory constructor that +network request. It includes` a factory constructor that creates a `Post` from json. Converting JSON by hand is only one option. For more information, -please see the full article on [JSON and +see the full article on [JSON and serialization](/docs/development/data-and-backend/json). @@ -93,14 +93,14 @@ class Post { ### Convert the `http.Response` to a `Post` -Now, update the `fetchPost` function to return a `Future`. To do so, -you'll need to: +Now, update the `fetchPost()` function to return a `Future`. +To do so, you need to: 1. Convert the response body into a json `Map` with the `dart:convert` package. 2. If the server returns an "OK" response with a status code of 200, convert - the json `Map` into a `Post` using the `fromJson` factory method. - 3. If the server returns an unexpected response, throw an error + the json `Map` into a `Post` using the `fromJson()` factory method. + 3. If the server returns an unexpected response, throw an error. ```dart @@ -109,7 +109,7 @@ Future fetchPost() async { await http.get('https://jsonplaceholder.typicode.com/posts/1'); if (response.statusCode == 200) { - // If server returns an OK response, parse the JSON + // If server returns an OK response, parse the JSON. return Post.fromJson(json.decode(response.body)); } else { // If that response was not OK, throw an error. @@ -118,14 +118,14 @@ Future fetchPost() async { } ``` -Hooray! Now you've got a function that we can call to fetch a Post from the +Hooray! Now you've got a function that fetches a Post from the internet. -## 4. Fetch and Display the data +## 4. Fetch and display the data -In order to fetch the data and display it on screen, you can use the +To fetch the data and display it on screen, use the [`FutureBuilder`]({{site.api}}/flutter/widgets/FutureBuilder-class.html) -widget. The `FutureBuilder` Widget comes with Flutter and makes it easy +widget. The `FutureBuilder` widget comes with Flutter and makes it easy to work with async data sources. You must provide two parameters: @@ -146,7 +146,7 @@ FutureBuilder( return Text("${snapshot.error}"); } - // By default, show a loading spinner + // By default, show a loading spinner. return CircularProgressIndicator(); }, ); @@ -158,11 +158,11 @@ Although it's convenient, it's not recommended to put a call to an API in a `build()` method. Flutter calls the `build()` method every time it wants to change anything -in the view, and this happens surprisingly often. If you leave the fetch +in the view, and this happens surprisingly often. If you leave the fetch call in your `build()` method, you'll flood the API with unnecessary calls and slow down your app. -Here are some better options so it'll only hit the API when the page is +Here are some better options so it only hits the API when the page is initially loaded. ### Pass it into a `StatelessWidget` @@ -182,15 +182,15 @@ You can see a working example of this in the complete example below. ### Call it in the lifecycle of a `StatefulWidget`'s state -If your widget is stateful, you can call the fetch method in either the -[`initState`]({{site.api}}/flutter/widgets/State/initState.html) or -[`didChangeDependencies`]({{site.api}}/flutter/widgets/State/didChangeDependencies.html) +If your widget is stateful, call the fetch method in either the +[`initState()`]({{site.api}}/flutter/widgets/State/initState.html) or +[`didChangeDependencies()`]({{site.api}}/flutter/widgets/State/didChangeDependencies.html) methods. -`initState` is called exactly once and then never again. +The `initState` method is called exactly once and then never again. If you want to have the option of reloading the API in response to an [`InheritedWidget`]({{site.api}}/flutter/widgets/InheritedWidget-class.html) -changing, put the call into the `didChangeDependencies` method. See +changing, put the call into the `didChangeDependencies()` method. See [`State`]({{site.api}}/flutter/widgets/State-class.html) for more details. @@ -209,7 +209,7 @@ class _MyAppState extends State { ## Testing For information on how to test this functionality, -please see the following recipes: +see the following recipes: * [Introduction to unit testing](/docs/cookbook/testing/unit/introduction) * [Mock dependencies using Mockito](/docs/cookbook/testing/unit/mocking) @@ -228,7 +228,7 @@ Future fetchPost() async { await http.get('https://jsonplaceholder.typicode.com/posts/1'); if (response.statusCode == 200) { - // If the call to the server was successful, parse the JSON + // If the call to the server was successful, parse the JSON. return Post.fromJson(json.decode(response.body)); } else { // If that call was not successful, throw an error. @@ -282,7 +282,7 @@ class MyApp extends StatelessWidget { return Text("${snapshot.error}"); } - // By default, show a loading spinner + // By default, show a loading spinner. return CircularProgressIndicator(); }, ), diff --git a/src/docs/cookbook/networking/web-sockets.md b/src/docs/cookbook/networking/web-sockets.md index d2f9005d783..a1c9b61b138 100644 --- a/src/docs/cookbook/networking/web-sockets.md +++ b/src/docs/cookbook/networking/web-sockets.md @@ -12,8 +12,8 @@ In addition to normal HTTP requests, you can connect to servers using WebSockets. WebSockets allow for two-way communication with a server without polling. -In this example, you'll connect to a [test server provided by -websocket.org](http://www.websocket.org/echo.html). The server simply sends +In this example, connect to a [test server provided by +websocket.org](http://www.websocket.org/echo.html). The server sends back the same message you send to it. ## Directions @@ -26,7 +26,7 @@ back the same message you send to it. ## 1. Connect to a WebSocket server The [web_socket_channel]({{site.pub-pkg}}/web_socket_channel) -package provides the tools you'll need to connect to a WebSocket server. +package provides the tools you need to connect to a WebSocket server. The package provides a `WebSocketChannel` that allows you to both listen for messages from the server and push messages to the server. @@ -41,16 +41,16 @@ final channel = IOWebSocketChannel.connect('ws://echo.websocket.org'); ## 2. Listen for messages from the server -Now that you've established a connection, you can listen to messages from the +Now that you've established a connection, listen to messages from the server. After you send a message to the test server, it sends the same message back. -How to listen for messages and display them? In this example, you'll use +How to listen for messages and display them? In this example, use a [`StreamBuilder`]({{site.api}}/flutter/widgets/StreamBuilder-class.html) -Widget to listen for new messages and a +widget to listen for new messages, and a [`Text`]({{site.api}}/flutter/widgets/Text-class.html) -Widget to display them. +widget to display them. ```dart @@ -74,12 +74,12 @@ which returns a single async response, the `Stream` class can deliver many events over time. The [`StreamBuilder`]({{site.api}}/flutter/widgets/StreamBuilder-class.html) -Widget connects to a `Stream` and asks Flutter to rebuild every time it +widget connects to a `Stream` and asks Flutter to rebuild every time it receives an event using the given `builder` function. ## 3. Send Data to the Server -In order to send data to the server, `add` messages to the `sink` provided +To send data to the server, `add()` messages to the `sink` provided by the `WebSocketChannel`. @@ -87,14 +87,14 @@ by the `WebSocketChannel`. channel.sink.add('Hello!'); ``` -### How does this work +### How this works The `WebSocketChannel` provides a [`StreamSink`]({{site.api}}/flutter/dart-async/StreamSink-class.html) to push messages to the server. -The `StreamSink` class provides a general way to add sync or async events to a -data source. +The `StreamSink` class provides a general way to add sync or async +events to a data source. ## 4. Close the WebSocket connection diff --git a/src/docs/cookbook/persistence/key-value.md b/src/docs/cookbook/persistence/key-value.md index 20a1195789b..8bfc5d0d378 100644 --- a/src/docs/cookbook/persistence/key-value.md +++ b/src/docs/cookbook/persistence/key-value.md @@ -1,19 +1,20 @@ --- title: Storing key-value data on disk prev: - title: Reading and Writing Files + title: Reading and writing files path: /docs/cookbook/persistence/reading-writing-files next: title: Play and pause a video path: /docs/cookbook/plugins/play-video --- -If you have a relatively small collection of key-values that you'd like +If you have a relatively small collection of key-values to save, you can use the [shared_preferences]({{site.pub}}/packages/shared_preferences) plugin. -Normally you would have to write native platform integrations for storing -data on both platforms. Fortunately, the +Normally, +you would have to write native platform integrations for storing +data on both iOS and Android. Fortunately, the [shared_preferences]({{site.pub-pkg}}/shared_preferences) plugin can be used to persist key-value data on disk. The shared preferences plugin wraps `NSUserDefaults` on iOS and `SharedPreferences` on Android, @@ -22,13 +23,13 @@ providing a persistent store for simple data. ## Directions 1. Add the dependency - 2. Save Data - 3. Read Data - 4. Remove Data + 2. Save data + 3. Read data + 4. Remove data ## 1. Add the dependency -Before starting, you need to add the +Before starting, add the [shared_preferences]({{site.pub-pkg}}/shared_preferences) plugin to the `pubspec.yaml` file: @@ -67,13 +68,13 @@ For example, you can use the `getInt`, `getBool`, and `getString` methods. ```dart final prefs = await SharedPreferences.getInstance(); -// Try reading data from the counter key. If it does not exist, return 0. +// Try reading data from the counter key. If it doesn't exist, return 0. final counter = prefs.getInt('counter') ?? 0; ``` ## 4. Remove data -To delete data, use the `remove` method. +To delete data, use the `remove()` method. ```dart @@ -84,10 +85,10 @@ prefs.remove('counter'); ## Supported types -While it is easy and convenient to use key-value storage, it has limitations: +While it's easy and convenient to use key-value storage, it has limitations: * Only primitive types can be used: `int`, `double`, `bool`, `string` and - `stringList` + `stringList`. * It's not designed to store a lot of data. For more information about Shared Preferences on Android, see @@ -97,12 +98,12 @@ on the Android developers website. ## Testing support -It can be a good idea to test code that persists data using -`shared_preferences`. To do so, you'll need to mock out the +It's a good idea to test code that persists data using +`shared_preferences`. To do so, you need to mock out the `MethodChannel` used by the `shared_preferences` library. -You can populate `SharedPreferences` with initial values in your tests -by running the following code in a `setupAll` method in your test files: +Populate `SharedPreferences` with initial values in your tests +by running the following code in a `setupAll()` method in your test files: ```dart diff --git a/src/docs/cookbook/persistence/reading-writing-files.md b/src/docs/cookbook/persistence/reading-writing-files.md index ec5ae902d2f..5c2a6ec3b15 100644 --- a/src/docs/cookbook/persistence/reading-writing-files.md +++ b/src/docs/cookbook/persistence/reading-writing-files.md @@ -1,5 +1,5 @@ --- -title: Reading and Writing Files +title: Reading and writing files prev: title: Persist data with SQLite path: /docs/cookbook/persistence/sqlite @@ -8,12 +8,12 @@ next: path: /docs/cookbook/persistence/key-value --- -In some cases, it can be handy to read and write files to disk. -This can be used to persist data across app launches, -or to download data from the internet and save it for later offline use. +In some cases, you need to read and write files to disk. +For example, to persist data across app launches, +or download data from the internet and save it for later offline use. -In order to save files to disk, you'll need to combine the -[`path_provider` plugin]({{site.pub-pkg}}/path_provider) with +To save files to disk, combine the +[`path_provider`]({{site.pub-pkg}}/path_provider) plugin with the [`dart:io`]({{site.api}}/flutter/dart-io/dart-io-library.html) library. @@ -26,9 +26,9 @@ library. ## 1. Find the correct local path -In this example, you'll display a counter. When the counter changes, you'll +This example displays a counter. When the counter changes, write data on disk so you can read it again when the app loads. -Therefore, you must wonder: Where should I store this data? +Therefore, you might wonder: Where should I store this data? The [`path_provider`]({{site.pub-pkg}}/path_provider) plugin provides a platform-agnostic way to access commonly used locations on the @@ -42,11 +42,12 @@ locations: [`getCacheDir()`]({{site.android-dev}}/reference/android/content/Context#getCacheDir()) returns. * *Documents directory:* A directory for the app to store files that only - it can access. The system clears the directory only when the app is deleted. - On iOS, this corresponds to `NSDocumentDirectory`. On Android, this is the - `AppData` directory. + it can access. The system clears the directory only when the app + is deleted. + On iOS, this corresponds to the `NSDocumentDirectory`. + On Android, this is the `AppData` directory. -In this case, you'll want to store information in the documents directory. +This example stores information in the documents directory. You can find the path to the documents directory as follows: @@ -60,7 +61,7 @@ Future get _localPath async { ## 2. Create a reference to the file location -Once you know where to store the file, you'll need to create a reference to the +Once you know where to store the file, create a reference to the file's full location. You can use the [`File`]({{site.api}}/flutter/dart-io/File-class.html) class from the [dart:io]({{site.api}}/flutter/dart-io/dart-io-library.html) @@ -76,16 +77,16 @@ Future get _localFile async { ## 3. Write data to the file -Now that you have a `File` to work with, use it to read and write data. +Use the `File` to read and write data. First, write some data to the file. Since you're working with a counter, -you'll simply store the integer as a String. +store the integer as a String. ```dart Future writeCounter(int counter) async { final file = await _localFile; - // Write the file + // Write the file. return file.writeAsString('$counter'); } ``` @@ -101,12 +102,12 @@ Future readCounter() async { try { final file = await _localFile; - // Read the file + // Read the file. String contents = await file.readAsString(); return int.parse(contents); } catch (e) { - // If encountering an error, return 0 + // If encountering an error, return 0. return 0; } } @@ -114,23 +115,22 @@ Future readCounter() async { ## Testing -In order to test code that interacts with files, you'll need to Mock calls to -the `MethodChannel`. The `MethodChannel` is the class that Flutter uses to -communicate with the host platform. +To test code that interacts with files, you need to mock calls to +the `MethodChannel`—the class that +communicates with the host platform. For security reasons, +you can't directly interact with the filesystem on a device, +so you interact with the test environment's filesystem. -In these tests, you can't interact with the filesystem on a device. -You'll need to interact with the test environment's filesystem. - -To mock the method call, provide a `setupAll` function in the test file. +To mock the method call, provide a `setupAll()` function in the test file. This function runs before the tests are executed. ```dart setUpAll(() async { - // Create a temporary directory to work with + // Create a temporary directory. final directory = await Directory.systemTemp.createTemp(); - // Mock out the MethodChannel for the path_provider plugin + // Mock out the MethodChannel for the path_provider plugin. const MethodChannel('plugins.flutter.io/path_provider') .setMockMethodCallHandler((MethodCall methodCall) async { // If you're getting the apps documents directory, return the path to the diff --git a/src/docs/cookbook/persistence/sqlite.md b/src/docs/cookbook/persistence/sqlite.md index 382a895ca94..65d453e27ec 100644 --- a/src/docs/cookbook/persistence/sqlite.md +++ b/src/docs/cookbook/persistence/sqlite.md @@ -4,29 +4,29 @@ prev: title: Working with WebSockets path: /docs/cookbook/networking/web-sockets next: - title: Reading and Writing Files + title: Reading and writing files path: /docs/cookbook/persistence/reading-writing-files --- -If you write an app that needs to persist and query larger amounts of data on -the local device, consider using a database instead of a local file or key-value -store. In general, databases provide faster inserts, updates, and queries -compared to other local persistence solutions. +If writing an app that needs to persist and query larger amounts of data on +the local device, consider using a database instead of a local file or +key-value store. In general, databases provide faster inserts, updates, +and queries, compared to other local persistence solutions. Flutter apps can make use of the SQLite databases via the [`sqflite`]({{site.pub-pkg}}/sqflite) plugin available on pub. This recipe demonstrates the basics of using `sqflite` to insert, read, update, -and remove data about various Dogs! +and remove data about various Dogs. -If you are new to SQLite and SQL statements, please review the [SQLite Tutorial -site](http://www.sqlitetutorial.net/) to learn the basics before completing -this recipe. +If you are new to SQLite and SQL statements, review the [SQLite +Tutorial](http://www.sqlitetutorial.net/) to learn the basics before +completing this recipe. ## Directions 1. Add the dependencies 2. Define the `Dog` data model - 3. Open the Database + 3. Open the database 4. Create the `dogs` table 5. Insert a `Dog` into the database 6. Retrieve the list of Dogs @@ -37,10 +37,10 @@ this recipe. To work with SQLite databases, import the `sqflite` and `path` packages. - - The `sqflite` package provides classes and functions that allow you to - interact with a SQLite database. - - The `path` package provides functions that allow you to correctly define the - location to store the database on disk. + * The `sqflite` package provides classes and functions to + interact with a SQLite database. + * The `path` package provides functions to + define the location for storing the database on disk. ```yaml dependencies: @@ -52,10 +52,10 @@ dependencies: ## 2. Define the Dog data model -Before you create the table to store information on Dogs, take a few moments to +Before creating the table to store information on Dogs, take a few moments to define the data that needs to be stored. For this example, define a Dog class -that contains three pieces of data: A unique `id`, the `name`, and the `age` of -each dog. +that contains three pieces of data: +A unique `id`, the `name`, and the `age` of each dog. ```dart @@ -68,18 +68,18 @@ class Dog { } ``` -## 3. Open the Database +## 3. Open the database -Before you read and write data to the database, you need to open a connection +Before reading and writing data to the database, open a connection to the database. This involves two steps: - 1. Define the path to the database file using the `getDatabasesPath` from the - `sqflite` package combined with the `path` function from the `path` package - 2. Open the database with the `openDatabase` function from `sqflite` + 1. Define the path to the database file using `getDatabasesPath()` from the + `sqflite` package, combined with the `path` function from the `path` package. + 2. Open the database with the `openDatabase()` function from `sqflite`. ```dart -// Open the database and store the reference +// Open the database and store the reference. final Future database = openDatabase( // Set the path to the database. Note: Using the `join` function from the // `path` package is best practice to ensure the path is correctly @@ -90,21 +90,21 @@ final Future database = openDatabase( ## 4. Create the `dogs` table -Next, you need to create a table to store information about various Dogs. For -this example, create a table called `dogs` that defines the data that can be -stored. In this case, each `Dog` contains an `id`, `name`, and `age`. Therefore, -these will be represented as three columns in the `dogs` table. +Next, create a table to store information about various Dogs. +For this example, create a table called `dogs` that defines the data +that can be stored. Each `Dog` contains an `id`, `name`, and `age`. +Therefore, these are represented as three columns in the `dogs` table. - 1. The `id` is a Dart `int`, and will be stored as an `INTEGER` SQLite - Datatype. It is also good practice to use an `id` as the primary key for the - table to improve query and update times. - 2. The `name` is a Dart `String`, and will be stored as a `TEXT` SQLite - Datatype - 3. The `age` is also a Dart `int`, and will be stored as an `INTEGER` - Datatype + 1. The `id` is a Dart `int`, and is stored as an `INTEGER` SQLite + Datatype. It is also good practice to use an `id` as the primary + key for the table to improve query and update times. + 2. The `name` is a Dart `String`, and is stored as a `TEXT` SQLite + Datatype. + 3. The `age` is also a Dart `int`, and is stored as an `INTEGER` + Datatype. For more information about the available Datatypes that can be stored in a -SQLite database, please see [the official SQLite Datatypes +SQLite database, see [the official SQLite Datatypes documentation](https://www.sqlite.org/datatype3.html). @@ -112,9 +112,9 @@ documentation](https://www.sqlite.org/datatype3.html). final Future database = openDatabase( // Set the path to the database. join(await getDatabasesPath(), 'doggie_database.db'), - // When the database is first created, create a table to store dogs + // When the database is first created, create a table to store dogs. onCreate: (db, version) { - // Run the CREATE TABLE statement on the database + // Run the CREATE TABLE statement on the database. return db.execute( "CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)", ); @@ -128,18 +128,18 @@ final Future database = openDatabase( ## 5. Insert a Dog into the database Now that you have a database with a table suitable for storing information -about various dogs, it's time to read and write data! +about various dogs, it's time to read and write data. First, insert a `Dog` into the `dogs` table. This involves two steps: 1. Convert the `Dog` into a `Map` 2. Use the - [`insert`]({{site.pub-api}}/sqflite/latest/sqlite_api/DatabaseExecutor/insert.html) - method to store the `Map` in the `dogs` table + [`insert()`]({{site.pub-api}}/sqflite/latest/sqlite_api/DatabaseExecutor/insert.html) + method to store the `Map` in the `dogs` table. ```dart -// First, update the Dog class to include a `toMap` method. +// Update the Dog class to include a `toMap` method. class Dog { final int id; final String name; @@ -147,7 +147,7 @@ class Dog { Dog({this.id, this.name, this.age}); - // Convert the dog into a Map. The keys must correspond to the names of the + // Convert a Dog into a Map. The keys must correspond to the names of the // columns in the database. Map toMap() { return { @@ -158,12 +158,12 @@ class Dog { } } -// Next, define a function that inserts dogs into the database +// Define a function that inserts dogs into the database Future insertDog(Dog dog) async { - // Get a reference to the database + // Get a reference to the database. final Database db = await database; - // Insert the Dog into the correct table. You may also specify the + // Insert the Dog into the correct table. You might also specify the // `conflictAlgorithm` to use in case the same dog is inserted twice. // // In this case, replace any previous data. @@ -174,7 +174,7 @@ Future insertDog(Dog dog) async { ); } -// Now, you can create a Dog to and add it to the dogs table! +// Create a Dog and add it to the dogs table. final fido = Dog( id: 0, name: 'Fido', @@ -186,20 +186,20 @@ await insertDog(fido); ## 6. Retrieve the list of Dogs -Now that you have a `Dog` stored in the database, you can query the database -for a specific dog or a list of all dogs! This involves two steps: +Now that a `Dog` is stored in the database, query the database +for a specific dog or a list of all dogs. This involves two steps: - 1. Run a `query` against the `dogs` table. This will return a `List` - 2. Convert the `List` into a `List` + 1. Run a `query` against the `dogs` table. This returns a `List`. + 2. Convert the `List` into a `List`. ```dart -// A method that will retrieve all the dogs from the dogs table +// A method that retrieves all the dogs from the dogs table. Future> dogs() async { - // Get a reference to the database + // Get a reference to the database. final Database db = await database; - // Query the table for All The Dogs. + // Query the table for all The Dogs. final List> maps = await db.query('dogs'); // Convert the List into a List. @@ -212,81 +212,82 @@ Future> dogs() async { }); } -// Now, you can use the method above to retrieve all the dogs! -print(await dogs()); // Prints a list that include Fido +// Now, use the method above to retrieve all the dogs. +print(await dogs()); // Prints a list that include Fido. ``` ## 7. Update a `Dog` in the database -After you've inserted some information into the database, you may want to update -that information at a later time. To do so, use the -[`update`]({{site.pub-api}}/sqflite/latest/sqlite_api/DatabaseExecutor/update.html) +After inserting information into the database, +you might want to update that information at a later time. +To do so, use the +[`update()`]({{site.pub-api}}/sqflite/latest/sqlite_api/DatabaseExecutor/update.html) method from the `sqflite` library. This involves two steps: - 1. Convert the Dog into a Map - 2. Use a `where` clause to ensure you update the correct Dog + 1. Convert the Dog into a Map. + 2. Use a `where` clause to ensure you update the correct Dog. ```dart Future updateDog(Dog dog) async { - // Get a reference to the database + // Get a reference to the database. final db = await database; - // Update the given Dog + // Update the given Dog. await db.update( 'dogs', dog.toMap(), - // Ensure we only update the Dog with a matching id + // Ensure that the Dog has a matching id. where: "id = ?", - // Pass the Dog's id through as a whereArg to prevent SQL injection + // Pass the Dog's id as a whereArg to prevent SQL injection. whereArgs: [dog.id], ); } -// Now, you can update Fido's age! +// Update Fido's age. await updateDog(Dog( id: 0, name: 'Fido', age: 42, )); -// And you could print the updated results +// Print the updated results. print(await dogs()); // Prints Fido with age 42. ``` {{site.alert.warning}} -Always use `whereArgs` to pass arguments to a `where` statement. This -helps safeguard against SQL injection attacks. + Always use `whereArgs` to pass arguments to a `where` statement. + This helps safeguard against SQL injection attacks. -Do not use string interpolation, such as `where: "id = ${dog.id}"`! + Do not use string interpolation, such as `where: "id = ${dog.id}"`! {{site.alert.end}} ## 8. Delete a `Dog` from the database -In addition to inserting and updating information about Dogs, you can also -remove dogs from the database. To delete data, use the -[`delete`]({{site.pub-api}}/sqflite/latest/sqlite_api/DatabaseExecutor/delete.html) +In addition to inserting and updating information about Dogs, +you can also remove dogs from the database. To delete data, use the +[`delete()`]({{site.pub-api}}/sqflite/latest/sqlite_api/DatabaseExecutor/delete.html) method from the `sqflite` library. -In this portion, create a function that takes in an id and deletes the dog with +In this section, create a function that takes an id and deletes the dog with a matching id from the database. To make this work, you must provide a `where` clause to limit the records being deleted. ```dart Future deleteDog(int id) async { - // Get a reference to the database + // Get a reference to the database. final db = await database; - // Remove the Dog from the Database + // Remove the Dog from the Database. await db.delete( 'dogs', - // Use a `where` clause to delete a specific dog + // Use a `where` clause to delete a specific dog. where: "id = ?", - // Pass the Dog's id through as a whereArg to prevent SQL injection + // Pass the Dog's id as a whereArg to prevent SQL injection. whereArgs: [id], ); } @@ -296,10 +297,10 @@ Future deleteDog(int id) async { To run the example: - 1. Create a new Flutter project - 2. Add the `sqfite` and `path` packages to your `pubspec.yaml` - 3. Paste the following code into a new file called `lib/db_test.dart` - 4. Run the code with `flutter run lib/db_test.dart` + 1. Create a new Flutter project. + 2. Add the `sqfite` and `path` packages to your `pubspec.yaml`. + 3. Paste the following code into a new file called `lib/db_test.dart`. + 4. Run the code with `flutter run lib/db_test.dart`. ```dart import 'dart:async'; @@ -313,7 +314,7 @@ void main() async { // `path` package is best practice to ensure the path is correctly // constructed for each platform. join(await getDatabasesPath(), 'doggie_database.db'), - // When the database is first created, create a table to store dogs + // When the database is first created, create a table to store dogs. onCreate: (db, version) { return db.execute( "CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)", @@ -325,12 +326,12 @@ void main() async { ); Future insertDog(Dog dog) async { - // Get a reference to the database + // Get a reference to the database. final Database db = await database; - // Insert the Dog into the correct table. We will also specify the - // `conflictAlgorithm` to use in this case. If the same dog is inserted - // multiple times, it will replace the previous data. + // Insert the Dog into the correct table. Also specify the + // `conflictAlgorithm`. In this case, if the same dog is inserted + // multiple times, it replaces the previous data. await db.insert( 'dogs', dog.toMap(), @@ -339,10 +340,10 @@ void main() async { } Future> dogs() async { - // Get a reference to the database + // Get a reference to the database. final Database db = await database; - // Query the table for All The Dogs. + // Query the table for all The Dogs. final List> maps = await db.query('dogs'); // Convert the List into a List. @@ -356,30 +357,30 @@ void main() async { } Future updateDog(Dog dog) async { - // Get a reference to the database + // Get a reference to the database. final db = await database; - // Update the given Dog + // Update the given Dog. await db.update( 'dogs', dog.toMap(), - // Ensure we only update the Dog with a matching id + // Ensure that the Dog has a matching id. where: "id = ?", - // Pass the Dog's id through as a whereArg to prevent SQL injection + // Pass the Dog's id as a whereArg to prevent SQL injection. whereArgs: [dog.id], ); } Future deleteDog(int id) async { - // Get a reference to the database + // Get a reference to the database. final db = await database; - // Remove the Dog from the Database + // Remove the Dog from the database. await db.delete( 'dogs', - // Use a `where` clause to delete a specific dog + // Use a `where` clause to delete a specific dog. where: "id = ?", - // Pass the Dog's id through as a whereArg to prevent SQL injection + // Pass the Dog's id as a whereArg to prevent SQL injection. whereArgs: [id], ); } @@ -390,13 +391,13 @@ void main() async { age: 35, ); - // Insert a dog into the database + // Insert a dog into the database. await insertDog(fido); - // Print the list of dogs (only Fido for now) + // Print the list of dogs (only Fido for now). print(await dogs()); - // Update Fido's age and save it to the database + // Update Fido's age and save it to the database. fido = Dog( id: fido.id, name: fido.name, @@ -404,13 +405,13 @@ void main() async { ); await updateDog(fido); - // Print Fido's updated information + // Print Fido's updated information. print(await dogs()); - // Delete Fido from the Database + // Delete Fido from the database. await deleteDog(fido.id); - // Print the list of dogs (empty) + // Print the list of dogs (empty). print(await dogs()); } @@ -429,8 +430,8 @@ class Dog { }; } - // Implement toString to make it easier to see information about each dog when - // using the print statement. + // Implement toString to make it easier to see information about + // each dog when using the print statement. @override String toString() { return 'Dog{id: $id, name: $name, age: $age}'; diff --git a/src/docs/cookbook/plugins/picture-using-camera.md b/src/docs/cookbook/plugins/picture-using-camera.md index e4864e75e1a..b56f4dabb6a 100644 --- a/src/docs/cookbook/plugins/picture-using-camera.md +++ b/src/docs/cookbook/plugins/picture-using-camera.md @@ -1,5 +1,5 @@ --- -title: Take a picture using the Camera +title: Take a picture using the camera prev: title: Play and pause a video path: /docs/cookbook/plugins/play-video @@ -24,7 +24,7 @@ take a photo, and display it. 3. Create and initialize the `CameraController` 4. Use a `CameraPreview` to display the camera's feed 5. Take a picture with the `CameraController` - 6. Display the picture with an `Image` Widget + 6. Display the picture with an `Image` widget ## 1. Add the required dependencies @@ -45,25 +45,25 @@ dependencies: ## 2. Get a list of the available cameras -Next, you can get a list of available cameras using the `camera` plugin. +Next, get a list of available cameras using the `camera` plugin. ```dart // Obtain a list of the available cameras on the device. final cameras = await availableCameras(); -// Get a specific camera from the list of available cameras +// Get a specific camera from the list of available cameras. final firstCamera = cameras.first; ``` ## 3. Create and initialize the `CameraController` -Once you have a camera to work with, you need to create and initialize a -`CameraController`. This process establishes a connection to the device's camera -that allows you to control the camera and display a preview of the camera's -feed. +Once you have a camera, you need to create and initialize a +`CameraController`. This process establishes a connection to +the device's camera that allows you to control the camera +and display a preview of the camera's feed. -To achieve this, please: +To achieve this, do the following: 1. Create a `StatefulWidget` with a companion `State` class 2. Add a variable to the `State` class to store the `CameraController` @@ -74,7 +74,7 @@ To achieve this, please: ```dart -// A screen that takes in a list of Cameras and the Directory to store images. +// A screen that takes in a list of cameras and the Directory to store images. class TakePictureScreen extends StatefulWidget { final CameraDescription camera; @@ -89,56 +89,56 @@ class TakePictureScreen extends StatefulWidget { class TakePictureScreenState extends State { // Add two variables to the state class to store the CameraController and - // the Future + // the Future. CameraController _controller; Future _initializeControllerFuture; @override void initState() { super.initState(); - // In order to display the current output from the Camera, you need to + // To display the current output from the camera, // create a CameraController. _controller = CameraController( - // Get a specific camera from the list of available cameras + // Get a specific camera from the list of available cameras. widget.camera, - // Define the resolution to use + // Define the resolution to use. ResolutionPreset.medium, ); - // Next, you need to initialize the controller. This returns a Future + // Next, initialize the controller. This returns a Future. _initializeControllerFuture = _controller.initialize(); } @override void dispose() { - // Make sure to dispose of the controller when the Widget is disposed + // Dispose of the controller when the widget is disposed. _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - // Fill this out in the next steps + // Fill this out in the next steps. } } ``` {{site.alert.warning}} -If you do not initialize the `CameraController`, you *cannot* use the camera -to display a preview and take pictures. + If you do not initialize the `CameraController`, + you *cannot* use the camera to display a preview and take pictures. {{site.alert.end}} ## 4. Use a `CameraPreview` to display the camera's feed -Next, you can use the `CameraPreview` Widget from the `camera` package to +Next, use the `CameraPreview` widget from the `camera` package to display a preview of the camera's feed. -Remember: You must wait until the controller has finished initializing before -working with the camera. Therefore, you must wait for the -`_initializeControllerFuture` created in the previous step to complete before -showing a `CameraPreview`. +Remember: You must wait until the controller has finished initializing +before working with the camera. Therefore, you must wait for the +`_initializeControllerFuture()` created in the previous step to +complete before showing a `CameraPreview`. -You can use a +Use a [`FutureBuilder`]({{site.api}}/flutter/widgets/FutureBuilder-class.html) for exactly this purpose. @@ -146,15 +146,15 @@ for exactly this purpose. ```dart // You must wait until the controller is initialized before displaying the // camera preview. Use a FutureBuilder to display a loading spinner until the -// controller has finished initializing +// controller has finished initializing. FutureBuilder( future: _initializeControllerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - // If the Future is complete, display the preview + // If the Future is complete, display the preview. return CameraPreview(_controller); } else { - // Otherwise, display a loading indicator + // Otherwise, display a loading indicator. return Center(child: CircularProgressIndicator()); } }, @@ -163,8 +163,8 @@ FutureBuilder( ## 5. Take a picture with the `CameraController` -You can also use the `CameraController` to take pictures using the -[`takePicture`]({{site.pub-api}}/camera/latest/camera/CameraController/takePicture.html) +You can use the `CameraController` to take pictures using the +[`takePicture()`]({{site.pub-api}}/camera/latest/camera/CameraController/takePicture.html) method. In this example, create a `FloatingActionButton` that takes a picture using the `CameraController` when a user taps on the button. @@ -181,24 +181,24 @@ to handle any errors that might occur. ```dart FloatingActionButton( child: Icon(Icons.camera_alt), - // Provide an onPressed callback + // Provide an onPressed callback. onPressed: () async { // Take the Picture in a try / catch block. If anything goes wrong, // catch the error. try { - // Ensure the camera is initialized + // Ensure that the camera is initialized. await _initializeControllerFuture; // Construct the path where the image should be saved using the path // package. final path = join( - // In this example, store the picture in the temp directory. Find - // the temp directory using the `path_provider` plugin. + // Store the picture in the temp directory. + // Find the temp directory using the `path_provider` plugin. (await getTemporaryDirectory()).path, '${DateTime.now()}.png', ); - // Attempt to take a picture and log where it's been saved + // Attempt to take a picture and log where it's been saved. await _controller.takePicture(path); } catch (e) { // If an error occurs, log the error to the console. @@ -207,14 +207,14 @@ FloatingActionButton( }, ) ``` -## 6. Display the picture with an `Image` Widget +## 6. Display the picture with an `Image` widget If you take the picture successfully, you can then display the saved picture using an `Image` widget. In this case, the picture is stored as a file on the device. -Therefore, you must provide a `File` to the `Image.file` constructor. You -can create an instance of the `File` class by passing in the path you created in +Therefore, you must provide a `File` to the `Image.file` constructor. +You can create an instance of the `File` class by passing the path created in the previous step. @@ -222,7 +222,7 @@ the previous step. Image.file(File('path/to/my/picture.png')) ``` -## Complete Example +## Complete example ```dart import 'dart:async'; @@ -237,21 +237,21 @@ Future main() async { // Obtain a list of the available cameras on the device. final cameras = await availableCameras(); - // Get a specific camera from the list of available cameras + // Get a specific camera from the list of available cameras. final firstCamera = cameras.first; runApp( MaterialApp( theme: ThemeData.dark(), home: TakePictureScreen( - // Pass the appropriate camera to the TakePictureScreen Widget + // Pass the appropriate camera to the TakePictureScreen widget. camera: firstCamera, ), ), ); } -// A screen that allows users to take a picture using a given camera +// A screen that allows users to take a picture using a given camera. class TakePictureScreen extends StatefulWidget { final CameraDescription camera; @@ -271,22 +271,22 @@ class TakePictureScreenState extends State { @override void initState() { super.initState(); - // In order to display the current output from the Camera, you need to + // To display the current output from the Camera, // create a CameraController. _controller = CameraController( - // Get a specific camera from the list of available cameras + // Get a specific camera from the list of available cameras. widget.camera, - // Define the resolution to use + // Define the resolution to use. ResolutionPreset.medium, ); - // Next, you need to initialize the controller. This returns a Future + // Next, initialize the controller. This returns a Future. _initializeControllerFuture = _controller.initialize(); } @override void dispose() { - // Make sure to dispose of the controller when the Widget is disposed + // Dispose of the controller when the widget is disposed. _controller.dispose(); super.dispose(); } @@ -295,44 +295,44 @@ class TakePictureScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Take a picture')), - // You must wait until the controller is initialized before displaying the - // camera preview. Use a FutureBuilder to display a loading spinner until - // the controller has finished initializing + // Wait until the controller is initialized before displaying the + // camera preview. Use a FutureBuilder to display a loading spinner + // until the controller has finished initializing. body: FutureBuilder( future: _initializeControllerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - // If the Future is complete, display the preview + // If the Future is complete, display the preview. return CameraPreview(_controller); } else { - // Otherwise, display a loading indicator + // Otherwise, display a loading indicator. return Center(child: CircularProgressIndicator()); } }, ), floatingActionButton: FloatingActionButton( child: Icon(Icons.camera_alt), - // Provide an onPressed callback + // Provide an onPressed callback. onPressed: () async { // Take the Picture in a try / catch block. If anything goes wrong, // catch the error. try { - // Ensure the camera is initialized + // Ensure that the camera is initialized. await _initializeControllerFuture; - // Construct the path where the image should be saved using the path - // package. + // Construct the path where the image should be saved using the + // pattern package. final path = join( - // In this example, store the picture in the temp directory. Find - // the temp directory using the `path_provider` plugin. + // Store the picture in the temp directory. + // Find the temp directory using the `path_provider` plugin. (await getTemporaryDirectory()).path, '${DateTime.now()}.png', ); - // Attempt to take a picture and log where it's been saved + // Attempt to take a picture and log where it's been saved. await _controller.takePicture(path); - // If the picture was taken, display it on a new screen + // If the picture was taken, display it on a new screen. Navigator.push( context, MaterialPageRoute( @@ -349,7 +349,7 @@ class TakePictureScreenState extends State { } } -// A Widget that displays the picture taken by the user +// A widget that displays the picture taken by the user. class DisplayPictureScreen extends StatelessWidget { final String imagePath; @@ -360,7 +360,7 @@ class DisplayPictureScreen extends StatelessWidget { return Scaffold( appBar: AppBar(title: Text('Display the Picture')), // The image is stored as a file on the device. Use the `Image.file` - // constructor with the given path to display the image + // constructor with the given path to display the image. body: Image.file(File(imagePath)), ); } diff --git a/src/docs/cookbook/plugins/play-video.md b/src/docs/cookbook/plugins/play-video.md index 16ad98b3b40..55d5af60e81 100644 --- a/src/docs/cookbook/plugins/play-video.md +++ b/src/docs/cookbook/plugins/play-video.md @@ -4,15 +4,15 @@ prev: title: Storing key-value data on disk path: /docs/cookbook/persistence/key-value next: - title: Take a picture using the Camera + title: Take a picture using the camera path: /docs/cookbook/plugins/picture-using-camera --- Playing videos is a common task in app development, and Flutter apps are no -exception. In order to play videos, the Flutter team provides the +exception. To play videos, the Flutter team provides the [`video_player`]({{site.pub-pkg}}/video_player) plugin. You can -use the `video_player` plugin to play videos stored on the file system, as an -asset, or from the internet. +use the `video_player` plugin to play videos stored on the file system, +as an asset, or from the internet. On iOS, the `video_player` plugin makes use of [`AVPlayer`](https://developer.apple.com/documentation/avfoundation/avplayer) to @@ -49,9 +49,9 @@ from the internet. To do so, update your `android` and `ios` configurations. ### Android -Add the following permission to the `AndroidManifest.xml` just after the -`` definition. The `AndroidManifest.xml` can be found at `/android/app/src/main/AndroidManifest.xml` +Add the following permission to the `AndroidManifest.xml` file just after the +`` definition. The `AndroidManifest.xml` file is found at +`/android/app/src/main/AndroidManifest.xml`. ```xml @@ -66,7 +66,7 @@ root>/android/app/src/main/AndroidManifest.xml` ### iOS -For iOS, you need to add the following to your `Info.plist` file found at +For iOS, add the following to the `Info.plist` file found at `/ios/Runner/Info.plist`. @@ -79,21 +79,22 @@ For iOS, you need to add the following to your `Info.plist` file found at ``` {{site.alert.warning}} -The `video_player` plugin does not work on iOS simulators. You must test videos -on real iOS devices. + The `video_player` plugin doesn't work on iOS simulators. + You must test videos on real iOS devices. {{site.alert.end}} ## 3. Create and initialize a `VideoPlayerController` Now that you have the `video_player` plugin installed with the correct -permissions, you need to create a `VideoPlayerController`. The +permissions, create a `VideoPlayerController`. The `VideoPlayerController` class allows you to connect to different types of videos and control playback. -Before you can play videos, you must also `initialize` the controller. This -establishes the connection to the video and prepare the controller for playback. +Before you can play videos, you must also `initialize` the controller. +This establishes the connection to the video and prepare the +controller for playback. -To create and initialize the `VideoPlayerController`, please: +To create and initialize the `VideoPlayerController` do the following: 1. Create a `StatefulWidget` with a companion `State` class 2. Add a variable to the `State` class to store the `VideoPlayerController` @@ -131,7 +132,7 @@ class _VideoPlayerScreenState extends State { @override void dispose() { - // Ensure you dispose the VideoPlayerController to free up resources + // Ensure disposing of the VideoPlayerController to free up resources. _controller.dispose(); super.dispose(); @@ -139,47 +140,47 @@ class _VideoPlayerScreenState extends State { @override Widget build(BuildContext context) { - // Show the video in the next step + // Complete the code in the next step. } } ``` ## 4. Display the video player -Now, it's time to display the video. The `video_player` plugin provides the +Now, display the video. The `video_player` plugin provides the [`VideoPlayer`]({{site.pub-api}}/video_player/latest/video_player/VideoPlayer-class.html) -Widget to display the video initialized by the `VideoPlayerController`. By -default, the `VideoPlayer` Widget takes up as much space as possible. This -often isn't ideal for videos because they are meant to be displayed in a +widget to display the video initialized by the `VideoPlayerController`. +By default, the `VideoPlayer` widget takes up as much space as possible. +This often isn't ideal for videos because they are meant to be displayed in a specific aspect ratio, such as 16x9 or 4x3. -Therefore, you can wrap the `VideoPlayer` widget in an +Therefore, wrap the `VideoPlayer` widget in an [`AspectRatio`]({{site.api}}/flutter/widgets/AspectRatio-class.html) -widget to ensure the video is the correct proportions. +widget to ensure that the video has the correct proportions. Furthermore, you must display the `VideoPlayer` widget after the -`_initializeVideoPlayerFuture` completes. You can use a `FutureBuilder` to -display a loading spinner until finishes initializing. Note: initializing the -controller does not begin playback. +`_initializeVideoPlayerFuture()` completes. Use `FutureBuilder` to +display a loading spinner until the controller finishes initializing. +Note: initializing the controller does not begin playback. ```dart -// Use a FutureBuilder to display a loading spinner while you wait for the +// Use a FutureBuilder to display a loading spinner while waiting for the // VideoPlayerController to finish initializing. FutureBuilder( future: _initializeVideoPlayerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { // If the VideoPlayerController has finished initialization, use - // the data it provides to limit the Aspect Ratio of the VideoPlayer + // the data it provides to limit the aspect ratio of the VideoPlayer. return AspectRatio( aspectRatio: _controller.value.aspectRatio, - // Use the VideoPlayer widget to display the video + // Use the VideoPlayer widget to display the video. child: VideoPlayer(_controller), ); } else { // If the VideoPlayerController is still initializing, show a - // loading spinner + // loading spinner. return Center(child: CircularProgressIndicator()); } }, @@ -190,27 +191,27 @@ FutureBuilder( By default, the video starts in a paused state. To begin playback, call the -[`play`]({{site.pub-api}}/video_player/latest/video_player/VideoPlayerController/play.html) +[`play()`]({{site.pub-api}}/video_player/latest/video_player/VideoPlayerController/play.html) method provided by the `VideoPlayerController`. To pause playback, call the -[`pause`]({{site.pub-api}}/video_player/latest/video_player/VideoPlayerController/pause.html) +[`pause()`]({{site.pub-api}}/video_player/latest/video_player/VideoPlayerController/pause.html) method. For this example, add a `FloatingActionButton` to your app that displays a play -or pause icon depending on the situation. When the user taps the button, play -the video if it's currently paused, or pause the video if it's playing. +or pause icon depending on the situation. When the user taps the button, +play the video if it's currently paused, or pause the video if it's playing. ```dart FloatingActionButton( onPressed: () { - // Wrap the play or pause in a call to `setState`. This ensures the correct - // icon is shown + // Wrap the play or pause in a call to `setState`. This ensures the + // correct icon is shown setState(() { // If the video is playing, pause it. if (_controller.value.isPlaying) { _controller.pause(); } else { - // If the video is paused, play it + // If the video is paused, play it. _controller.play(); } }); @@ -222,7 +223,7 @@ FloatingActionButton( ) ``` -## Complete Example +## Complete example ```dart import 'dart:async'; @@ -262,10 +263,10 @@ class _VideoPlayerScreenState extends State { 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4', ); - // Initialize the controller and store the Future for later use + // Initielize the controller and store the Future for later use. _initializeVideoPlayerFuture = _controller.initialize(); - // Use the controller to loop the video + // Use the controller to loop the video. _controller.setLooping(true); super.initState(); @@ -273,7 +274,7 @@ class _VideoPlayerScreenState extends State { @override void dispose() { - // Ensure you dispose the VideoPlayerController to free up resources + // Ensure disposing of the VideoPlayerController to free up resources. _controller.dispose(); super.dispose(); @@ -285,22 +286,22 @@ class _VideoPlayerScreenState extends State { appBar: AppBar( title: Text('Butterfly Video'), ), - // Use a FutureBuilder to display a loading spinner while you wait for the + // Use a FutureBuilder to display a loading spinner while waiting for the // VideoPlayerController to finish initializing. body: FutureBuilder( future: _initializeVideoPlayerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { // If the VideoPlayerController has finished initialization, use - // the data it provides to limit the Aspect Ratio of the Video + // the data it provides to limit the aspect ratio of the video. return AspectRatio( aspectRatio: _controller.value.aspectRatio, - // Use the VideoPlayer widget to display the video + // Use the VideoPlayer widget to display the video. child: VideoPlayer(_controller), ); } else { // If the VideoPlayerController is still initializing, show a - // loading spinner + // loading spinner. return Center(child: CircularProgressIndicator()); } }, @@ -308,13 +309,13 @@ class _VideoPlayerScreenState extends State { floatingActionButton: FloatingActionButton( onPressed: () { // Wrap the play or pause in a call to `setState`. This ensures the - // correct icon is shown + // correct icon is shown. setState(() { // If the video is playing, pause it. if (_controller.value.isPlaying) { _controller.pause(); } else { - // If the video is paused, play it + // If the video is paused, play it. _controller.play(); } }); diff --git a/src/docs/cookbook/testing/integration/introduction.md b/src/docs/cookbook/testing/integration/introduction.md index 686a2ea4997..6a709012c51 100644 --- a/src/docs/cookbook/testing/integration/introduction.md +++ b/src/docs/cookbook/testing/integration/introduction.md @@ -2,30 +2,32 @@ title: An introduction to integration testing short-title: Introduction prev: - title: Take a picture using the Camera + title: Take a picture using the camera path: /docs/cookbook/plugins/picture-using-camera next: title: Performance profiling path: /docs/cookbook/testing/integration/profiling --- -Unit tests and Widget tests are handy for testing individual classes, functions, -or Widgets. However, they generally don't test how individual pieces work -together as a whole or capture the performance of an application running on a -real device. These tasks are performed with *integration tests*. +Unit tests and widget tests are handy for testing individual classes, +functions, or widgets. However, they generally don't test how +individual pieces work together as a whole or capture the performance +of an application running on a real device. These tasks are performed +with *integration tests*. -Integration tests work as a pair: first, deploy an instrumented application to a -real device or emulator and then "drive" the application from a separate test -suite, checking to make sure everything is correct along the way. +Integration tests work as a pair: first, deploy an instrumented application +to a real device or emulator and then "drive" the application from a +separate test suite, checking to make sure everything is correct along +the way. -To create this test pair, we can use the +To create this test pair, use the [flutter_driver]({{site.api}}/flutter/flutter_driver/flutter_driver-library.html) package. It provides tools to create instrumented apps and drive those apps from a test suite. -In this recipe, we'll learn how to test a counter app. It will demonstrate -how to setup integration tests, how to verify specific text is displayed by the -app, how to tap on specific Widgets, and how to run integration tests. +In this recipe, you learn how to test a counter app. It demonstrates +how to setup integration tests, how to verify specific text is displayed +by the app, how to tap specific widgets, and how to run integration tests. ### Directions @@ -38,14 +40,14 @@ app, how to tap on specific Widgets, and how to run integration tests. ### 1. Create an app to test -First, we'll create an app that we can test! In this example, we'll test the +First, create an app for testing. In this example, test the counter app produced by the `flutter create` command. This app allows a user to tap on a button to increase a counter. -Furthermore, we'll also need to provide a +Furthermore, provide a [`ValueKey`]({{site.api}}/flutter/foundation/ValueKey-class.html) to -the `Text` and `FloatingActionButton` Widgets. This allows us to identify -and interact with these specific Widgets inside the test suite. +the `Text` and `FloatingActionButton` widgets. This allows identifying +and interacting with these specific widgets inside the test suite. ```dart import 'package:flutter/material.dart'; @@ -95,9 +97,9 @@ class _MyHomePageState extends State { ), Text( '$_counter', - // Provide a Key to this specific Text Widget. This allows us - // to identify this specific Widget from inside our test suite and - // read the text. + // Provide a Key to this specific Text widget. This allows + // identifing the widget from inside the test suite, + // and reading the text. key: Key('counter'), style: Theme.of(context).textTheme.display1, ), @@ -105,8 +107,8 @@ class _MyHomePageState extends State { ), ), floatingActionButton: FloatingActionButton( - // Provide a Key to this the button. This allows us to find this - // specific button and tap it inside the test suite. + // Provide a Key to this button. This allows finding this + // specific button inside the test suite, and tapping it. key: Key('increment'), onPressed: _incrementCounter, tooltip: 'Increment', @@ -119,11 +121,11 @@ class _MyHomePageState extends State { ### 2. Add the `flutter_driver` dependency -Next, we'll need the `flutter_driver` package to write integration tests. We -can add the `flutter_driver` dependency to the `dev_dependencies` section of -our apps's `pubspec.yaml` file. +Next, use the `flutter_driver` package to write integration tests. +Add the `flutter_driver` dependency to the `dev_dependencies` section of +the apps's `pubspec.yaml` file. -We also add the `test` dependency in order to use actual test functions and +Also add the `test` dependency in order to use actual test functions and assertions. ```yaml @@ -136,21 +138,23 @@ dev_dependencies: ### 3. Create the test files Unlike unit and widget tests, integration test suites do not run in the same -process as the app being tested. Therefore, we need to create two files that +process as the app being tested. Therefore, create two files that reside in the same directory. By convention, the directory is named `test_driver`. - 1. The first file contains an "instrumented" version of the app. The - instrumentation allows us to "drive" the app and record performance profiles - from a test suite. This file can be given any name that makes sense. For this - example, create a file called `test_driver/app.dart`. - 2. The second file contains the test suite, which drives the app and verifies - it works as expected. The test suite can also record performance profiles. - The name of the test file must correspond to the name of the file that - contains the instrumented app, with `_test` added at the end. Therefore, - create a second file called `test_driver/app_test.dart`. + 1. The first file contains an "instrumented" version of the app. + The instrumentation allows you to "drive" the app and record + performance profiles from a test suite. This file can have any + name that makes sense. For this example, create a file called + `test_driver/app.dart`. + 2. The second file contains the test suite, which drives the app and + verifies it works as expected. The test suite also records + performance profiles. The name of the test file must correspond + to the name of the file that contains the instrumented app, + with `_test` added at the end. Therefore, + create a second file called `test_driver/app_test.dart`. -This leaves us with the following directory structure: +This creates the following directory structure: ``` counter_app/ @@ -164,12 +168,12 @@ counter_app/ ### 4. Instrument the app -Now, we can instrument the app. This will involve two steps: +Now, instrument the app. This involves two steps: 1. Enable the flutter driver extensions 2. Run the app -We will add this code inside the `test_driver/app.dart` file. +Add this code inside the `test_driver/app.dart` file. ```dart @@ -177,36 +181,36 @@ import 'package:flutter_driver/driver_extension.dart'; import 'package:counter_app/main.dart' as app; void main() { - // This line enables the extension + // This line enables the extension. enableFlutterDriverExtension(); - // Call the `main()` function of your app or call `runApp` with any widget you - // are interested in testing. + // Call the `main()` function of the app, or call `runApp` with + // any widget you are interested in testing. app.main(); } ``` ### 5. Write the tests -Now that we have an instrumented app, we can write tests for it! This -will involve four steps: +Now that you have an instrumented app, you can write tests for it. +This involves four steps: 1. Create [`SeralizableFinders`]({{site.api}}/flutter/flutter_driver/CommonFinders-class.html) - to locate specific Widgets - 2. Connect to the app before our tests run in the `setUpAll` function + to locate specific widgets + 2. Connect to the app before our tests run in the `setUpAll()` function 3. Test the important scenarios - 4. Disconnect from the app in the `teardownAll` function after our tests - complete + 4. Disconnect from the app in the `teardownAll()` function after the tests + complete ```dart -// Imports the Flutter Driver API +// Imports the Flutter Driver API. import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; void main() { group('Counter App', () { - // First, define the Finders. We can use these to locate Widgets from the + // First, define the Finders and use them to locate widgets from the // test suite. Note: the Strings provided to the `byValueKey` method must // be the same as the Strings we used for the Keys in step 1. final counterTextFinder = find.byValueKey('counter'); @@ -214,12 +218,12 @@ void main() { FlutterDriver driver; - // Connect to the Flutter driver before running any tests + // Connect to the Flutter driver before running any tests. setUpAll(() async { driver = await FlutterDriver.connect(); }); - // Close the connection to the driver after the tests have completed + // Close the connection to the driver after the tests have completed. tearDownAll(() async { if (driver != null) { driver.close(); @@ -232,10 +236,10 @@ void main() { }); test('increments the counter', () async { - // First, tap on the button + // First, tap the button. await driver.tap(buttonFinder); - // Then, verify the counter text has been incremented by 1 + // Then, verify the counter text is incremented by 1. expect(await driver.getText(counterTextFinder), "1"); }); }); @@ -244,9 +248,9 @@ void main() { ### 6. Run the tests -Now that we have an instrumented app and a test suite, we can run the tests! -First, be sure to launch an Android Emulator, iOS Simulator, or connect your -computer to a real iOS / Android device. +Now that you have an instrumented app and a test suite, run the tests. +First, be sure to launch an Android Emulator, iOS Simulator, +or connect your computer to a real iOS / Android device. Then, run the following command from the root of the project: @@ -256,6 +260,6 @@ flutter drive --target=test_driver/app.dart This command: - 1. builds the `--target` app and installs it on the emulator / device - 2. launches the app - 3. runs the `app_test.dart` test suite located in `test_driver/` folder + 1. Builds the `--target` app and installs it on the emulator / device. + 2. Launches the app. + 3. Runs the `app_test.dart` test suite located in `test_driver/` folder. diff --git a/src/docs/cookbook/testing/integration/profiling.md b/src/docs/cookbook/testing/integration/profiling.md index 8d69463a6ac..da837747c2d 100644 --- a/src/docs/cookbook/testing/integration/profiling.md +++ b/src/docs/cookbook/testing/integration/profiling.md @@ -8,21 +8,21 @@ next: path: /docs/cookbook/testing/integration/scrolling --- -When it comes to mobile apps, performance is critical to user experience. Users -expect apps to have smooth scrolling and meaningful animations free of -stuttering or skipped frames, known as "jank." How can we ensure our apps are -free of jank on a wide variety of devices? - -There are two options: first, we could manually test the app on different -devices. While that approach might work for a smaller app, it will become more -cumbersome as an app grows in size. Alternatively, we can run an integration -test that performs a specific task and record a performance timeline. Then, we -can examine the results to determine whether or not a specific section of our -app needs to be improved. - -In this recipe, we'll learn how to write a test that records a performance -timeline while performing a specific task and saves a summary of the results to -a local file. +When it comes to mobile apps, performance is critical to user experience. +Users expect apps to have smooth scrolling and meaningful animations free of +stuttering or skipped frames, known as "jank." How to ensure that your app +is free of jank on a wide variety of devices? + +There are two options: first, manually test the app on different devices. +While that approach might work for a smaller app, it becomes more +cumbersome as an app grows in size. Alternatively, run an integration +test that performs a specific task and records a performance timeline. +Then, examine the results to determine whether a specific section of +the app needs to be improved. + +In this recipe, learn how to write a test that records a performance +timeline while performing a specific task and saves a summary of the +results to a local file. ### Directions @@ -34,34 +34,34 @@ a local file. ### 1. Write a test that scrolls through a list of items -In this recipe, we'll record the performance of an app as it scrolls through a -list of items. In order to focus on performance profiling, this recipe builds -upon the -[Scrolling in integration tests](/docs/cookbook/testing/integration/scrolling) -recipe. +In this recipe, record the performance of an app as it scrolls through a +list of items. To focus on performance profiling, this recipe builds +on the +[Scrolling](/docs/cookbook/testing/integration/scrolling) recipe +in integration tests. -Please follow the instructions in that recipe to create an app, instrument the -app, and write a test to verify everything works as expected. +Follow the instructions in that recipe to create an app, instrument the +app, and write a test to verify that everything works as expected. ### 2. Record the performance of the app -Next, we need to record the performance of the app as it scrolls through the -list. To achieve this task, we can use the -[`traceAction`]({{site.api}}/flutter/flutter_driver/FlutterDriver/traceAction.html) +Next, record the performance of the app as it scrolls through the +list. To achieve this task, use the +[`traceAction()`]({{site.api}}/flutter/flutter_driver/FlutterDriver/traceAction.html) method provided by the [`FlutterDriver`]({{site.api}}/flutter/flutter_driver/FlutterDriver-class.html) class. This method runs the provided function and records a [`Timeline`]({{site.api}}/flutter/flutter_driver/Timeline-class.html) -with detailed information about the performance of the app. In this example, we -provide a function that scrolls through the list of items, ensuring a specific -item is displayed. When the function completes, the `traceAction` method returns -a `Timeline`. +with detailed information about the performance of the app. This example +provides a function that scrolls through the list of items, +ensuring that a specific item is displayed. When the function completes, +the `traceAction()` method returns a `Timeline`. ```dart -// Record a performance timeline as we scroll through the list of items +// Record a performance timeline as the app scrolls through the list of items. final timeline = await driver.traceAction(() async { await driver.scrollUntilVisible( listFinder, @@ -75,21 +75,21 @@ final timeline = await driver.traceAction(() async { ### 3. Save the results to disk -Now that we've captured a performance timeline, we need a way to review it! -The `Timeline` object provides detailed information about all of the events that -took place, but it does not provide a convenient way to review the results. +Now that you've captured a performance timeline, you need a way to review it. +The `Timeline` object provides detailed information about all of the events +that took place, but it doesn't provide a convenient way to review the results. -Therefore, we can convert the `Timeline` into a +Therefore, convert the `Timeline` into a [`TimelineSummary`]({{site.api}}/flutter/flutter_driver/TimelineSummary-class.html). The `TimelineSummary` can perform two tasks that make it easier to review the results: - 1. It can write a json document on disk that summarizes the data contained - within the `Timeline`. This summary includes information about the number of - skipped frames, slowest build times, and more. - 2. It can save the complete `Timeline` as a json file on disk. This file can - be opened with the Chrome browser's tracing tools found at - [chrome://tracing](chrome://tracing). + 1. Writing a json document on disk that summarizes the data contained + within the `Timeline`. This summary includes information about the + number of skipped frames, slowest build times, and more. + 2. Saving the complete `Timeline` as a json file on disk. This file can + be opened with the Chrome browser's tracing tools found at + [chrome://tracing](chrome://tracing). ```dart @@ -97,7 +97,7 @@ results: // understand. final summary = new TimelineSummary.summarize(timeline); -// Then, save the summary to disk +// Then, save the summary to disk. summary.writeSummaryToFile('scrolling_summary', pretty: true); // Optionally, write the entire timeline to disk in a json format. This @@ -108,8 +108,8 @@ summary.writeTimelineToFile('scrolling_timeline', pretty: true); ### 4. Run the test -After we've configured our test to capture a performance `Timeline` and save a -summary of the results to disk, we can run the test with the following command: +After configuring the test to capture a performance `Timeline` and save a +summary of the results to disk, run the test with the following command: ``` flutter drive --target=test_driver/app.dart @@ -121,16 +121,16 @@ After the test completes successfully, the `build` directory at the root of the project contains two files: 1. `scrolling_summary.timeline_summary.json` contains the summary. Open - the file with any text editor to review the information contained within. - With a more advanced setup, we could save a summary every time the test - runs and create a graph of the results. + Open the file with any text editor to review the information contained + within. With a more advanced setup, you could save a summary every + time the test runs and create a graph of the results. 2. `scrolling_timeline.timeline.json` contains the complete timeline data. - Open the file using the Chrome browser's tracing tools found at - [chrome://tracing](chrome://tracing). The tracing tools provide a - convenient interface for inspecting the timeline data in order to discover - the source of a performance issue. + Open the file using the Chrome browser's tracing tools found at + [chrome://tracing](chrome://tracing). The tracing tools provide a + convenient interface for inspecting the timeline data to discover + the source of a performance issue. -#### Summary Example +#### Summary example ```json { @@ -178,7 +178,8 @@ void main() { final listFinder = find.byValueKey('long_list'); final itemFinder = find.byValueKey('item_50_text'); - // Record a performance profile as we scroll through the list of items + // Record a performance profile as the app scrolls through + // the list of items. final timeline = await driver.traceAction(() async { await driver.scrollUntilVisible( listFinder, @@ -189,16 +190,16 @@ void main() { expect(await driver.getText(itemFinder), 'Item 50'); }); - // Convert the Timeline into a TimelineSummary that's easier to read and - // understand. + // Convert the Timeline into a TimelineSummary that's easier to + // read and understand. final summary = new TimelineSummary.summarize(timeline); - // Then, save the summary to disk + // Then, save the summary to disk. summary.writeSummaryToFile('scrolling_summary', pretty: true); - // Optionally, write the entire timeline to disk in a json format. This - // file can be opened in the Chrome browser's tracing tools found by - // navigating to chrome://tracing. + // Optionally, write the entire timeline to disk in a json format. + // This file can be opened in the Chrome browser's tracing tools + // found by navigating to chrome://tracing. summary.writeTimelineToFile('scrolling_timeline', pretty: true); }); }); diff --git a/src/docs/cookbook/testing/integration/scrolling.md b/src/docs/cookbook/testing/integration/scrolling.md index fc0692b0ece..7cafebd9995 100644 --- a/src/docs/cookbook/testing/integration/scrolling.md +++ b/src/docs/cookbook/testing/integration/scrolling.md @@ -8,21 +8,22 @@ next: path: /docs/cookbook/testing/unit/introduction --- -Many apps feature lists of content, from email clients to music apps and beyond. -In order to verify that lists contain the content we expect using integration -tests, we need a way to scroll through lists to search for particular items. +Many apps feature lists of content, from email clients to music apps +and beyond. +To verify that lists contain the expected content using integration +tests, you need a way to scroll through lists to search for particular items. -In order to scroll through lists via integration tests, we can use the methods +To scroll through lists via integration tests, use the methods provided by the [`FlutterDriver`]({{site.api}}/flutter/flutter_driver/FlutterDriver-class.html) class, which is included in the [`flutter_driver`]({{site.api}}/flutter/flutter_driver/flutter_driver-library.html) package: -In this recipe, we'll learn how to scroll through a list of items in order to -verify a specific Widget is being displayed, and discuss the pros on cons of +In this recipe, you learn how to scroll through a list of items to +verify a specific widget is being displayed, and discuss the pros on cons of different approaches. If you're just getting started with integration testing, -please read through the [Introduction to integration +read through the [Introduction to integration testing](/docs/cookbook/testing/integration) recipe. ### Directions @@ -34,15 +35,15 @@ testing](/docs/cookbook/testing/integration) recipe. ### 1. Create an app with a list of items -In this recipe, we'll build an app that shows a long list of items. In order to -keep this recipe focused on testing, we'll use the app we created in the -[Working with long lists](/docs/cookbook/lists/long-lists) recipe. If you're unsure -of how to work with lists of content, please see that recipe for an -introduction. +This recipe builds an app that shows a long list of items. +To keep this recipe focused on testing, use the app created in the +[Working with long lists](/docs/cookbook/lists/long-lists) recipe. +If you're unsure of how to work with long lists, +see that recipe for an introduction. -As we did in the [Introduction to integration -testing](/docs/cookbook/testing/integration) recipe, we'll also add keys to the -widgets we want to interact with inside our integration tests. +As you did in the [Introduction to integration +testing](/docs/cookbook/testing/integration) recipe, add keys to the +widgets you want to interact with inside the integration tests. ```dart import 'package:flutter/foundation.dart'; @@ -70,17 +71,17 @@ class MyApp extends StatelessWidget { title: Text(title), ), body: ListView.builder( - // Add a key to the ListView. This allows us to find the list and - // scroll through it in our tests + // Add a key to the ListView. This makes it possible to + // find the list and scroll through it in the tests. key: Key('long_list'), itemCount: items.length, itemBuilder: (context, index) { return ListTile( title: Text( '${items[index]}', - // Add a key to the Text Widget for each item. This allows - // us to look for a particular item in the list and verify the - // text is correct + // Add a key to the Text widget for each item. This makes + // it possible to look for a particular item in the list + // and verify that the text is correct key: Key('item_${index}_text'), ), ); @@ -94,7 +95,7 @@ class MyApp extends StatelessWidget { ### 2. Instrument the app -Next, we'll need to create an instrumented version of our app. This code lives +Next, create an instrumented version of the app. This code lives in a file called `test_driver/app.dart`. @@ -103,58 +104,59 @@ import 'package:flutter_driver/driver_extension.dart'; import 'package:scrollable_app/main.dart' as app; void main() { - // This line enables the extension + // This line enables the extension. enableFlutterDriverExtension(); - // Call the `main()` function of your app or call `runApp` with any widget you - // are interested in testing. + // Call the `main()` function of the app or call `runApp` with + // any widget you are interested in testing. app.main(); } ``` ### 3. Write a test that scrolls through the list -Now, we can write our test! In this example, we need to scroll through the list +Now, you can write a test. In this example, scroll through the list of items and verify that a particular item exists in the list. The [`FlutterDriver`]({{site.api}}/flutter/flutter_driver/FlutterDriver-class.html) class provides three methods for scrolling through lists: - The - [`scroll`]({{site.api}}/flutter/flutter_driver/FlutterDriver/scroll.html) - method allows us to scroll through a specific list by a given amount. + [`scroll()`]({{site.api}}/flutter/flutter_driver/FlutterDriver/scroll.html) + method scrolls through a specific list by a given amount. - The - [`scrollIntoView`]({{site.api}}/flutter/flutter_driver/FlutterDriver/scrollIntoView.html) - method finds a specific Widget that's already been rendered, and will scroll - it completely into view. Some Widgets, such as + [`scrollIntoView()`]({{site.api}}/flutter/flutter_driver/FlutterDriver/scrollIntoView.html) + method finds a specific widget that's already been rendered, and scrolls + it completely into view. Some widgets, such as [`ListView.builder`]({{site.api}}/flutter/widgets/ListView/ListView.builder.html), render items on-demand. - The - [`scrollUntilVisible`]({{site.api}}/flutter/flutter_driver/FlutterDriver/scrollUntilVisible.html) - method scrolls through a list until a specific Widget is visible. - -While all three methods work for specific use-cases, `scrollUntilVisible` is -oftentimes the most robust option. Why? - - 1. If we use the `scroll` method alone, we might incorrectly assume the height - of each item in the list. This could lead to scrolling too much or too little. - 2. If we use the `scrollIntoView` method, we assume the Widget has been - instantiated and rendered. In order to verify our apps work on a broad range - of devices, we might run our integration tests against devices with different - screen sizes. Since `ListView.builder` will render items on-demand, - whether or not a particular Widget has been rendered can depend - on the size of the screen. - -Therefore, rather than assuming we know the height of all the items in a list, -or that a particular Widget will be rendered on all devices, we can use the -`scrollUntilVisible` method to repeatedly scroll through a list of items until -we find what we're looking for! - -Let's see how we can use the `scrollUntilVisible` method to look through the -list for a particular item! This code lives in a file called -`test_driver/app_test.dart`. + [`scrollUntilVisible()`]({{site.api}}/flutter/flutter_driver/FlutterDriver/scrollUntilVisible.html) + method scrolls through a list until a specific widget is visible. + +While all three methods work for specific use-cases, +`scrollUntilVisible` is oftentimes the most robust option. Why? + + 1. If using the `scroll()` method alone, you might incorrectly assume + the height of each item in the list. This could lead to scrolling + too much or too little. + 2. If using the `scrollIntoView()` method, you might assume that the + widget has been instantiated and rendered. To verify that an app + works on a broad range of devices, run integration tests against + devices with different screen sizes. Since `ListView.builder` + renders items on-demand, whether a particular widget has been + rendered can depend on the size of the screen. + +Therefore, rather than assuming that you know the height of all the items +in a list, or that a particular widget is rendered on all devices, +use the `scrollUntilVisible()` method to repeatedly scroll through +a list of items until you find what you're looking for. + +The following code shows how to use the `scrollUntilVisible()` method +to look through the list for a particular item. This code lives in a +file called `test_driver/app_test.dart`. ```dart -// Imports the Flutter Driver API +// Imports the Flutter Driver API. import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; @@ -162,12 +164,12 @@ void main() { group('Scrollable App', () { FlutterDriver driver; - // Connect to the Flutter driver before running any tests + // Connect to the Flutter driver before running any tests. setUpAll(() async { driver = await FlutterDriver.connect(); }); - // Close the connection to the driver after the tests have completed + // Close the connection to the driver after the tests have completed. tearDownAll(() async { if (driver != null) { await driver.close(); @@ -175,27 +177,27 @@ void main() { }); test('verifies the list contains a specific item', () async { - // Create two SerializableFinders. We will use these to locate specific - // Widgets displayed by the app. The names provided to the byValueKey - // method correspond to the Keys we provided to our Widgets in step 1. + // Create two SerializableFinders and use these to locate specific + // widgets displayed by the app. The names provided to the byValueKey + // method correspond to the Keys provided to the widgets in step 1. final listFinder = find.byValueKey('long_list'); final itemFinder = find.byValueKey('item_50_text'); await driver.scrollUntilVisible( - // Scroll through this list + // Scroll through the list listFinder, - // Until we find this item + // Until finding this item itemFinder, - // In order to scroll down the list, we need to provide a negative - // value to dyScroll. Ensure this value is a small enough increment to + // To scroll down the list, provide a negative value to dyScroll. + // Ensure that this value is a small enough increment to // scroll the item into view without potentially scrolling past it. // - // If you need to scroll through horizontal lists, provide a dxScroll - // argument instead + // To scroll through horizontal lists, provide a dxScroll + // property instead. dyScroll: -300.0, ); - // Verify the item contains the correct text + // Verify that the item contains the correct text. expect( await driver.getText(itemFinder), 'Item 50', @@ -207,8 +209,7 @@ void main() { ### 4. Run the test -Finally, we can run the test using the following command from the root of the -project: +Run the test using the following command from the root of the project: ``` flutter drive --target=test_driver/app.dart diff --git a/src/docs/cookbook/testing/unit/introduction.md b/src/docs/cookbook/testing/unit/introduction.md index 238d76c76e3..0d230658585 100644 --- a/src/docs/cookbook/testing/unit/introduction.md +++ b/src/docs/cookbook/testing/unit/introduction.md @@ -16,7 +16,7 @@ Unit tests are handy for verifying the behavior of a single function, method, or class. The [`test`]({{site.pub-pkg}}/test) package provides the core framework for writing unit tests, and the [`flutter_test`]({{site.api}}/flutter/flutter_test/flutter_test-library.html) -package provides additional utilities for testing Widgets. +package provides additional utilities for testing widgets. This recipe demonstrates the core features provided by the `test` package. For more information about the test package, see the @@ -34,10 +34,10 @@ documentation]({{site.github}}/dart-lang/test/blob/master/README.md). ## 1. Add the test dependency -If you're working on a Dart package that does not depend on Flutter, you -can import the `test` package. The test package provides the core functionality -for writing tests in Dart. This is the best approach when writing packages that -will be consumed by web, server, and Flutter apps. +If you're working on a Dart package that does not depend on Flutter, +you can import the `test` package. The test package provides the core +functionality for writing tests in Dart. This is the best approach when +writing packages consumed by web, server, and Flutter apps. ```yaml dev_dependencies: @@ -48,8 +48,8 @@ dev_dependencies: In this example, create two files: `counter.dart` and `counter_test.dart`. -The `counter.dart` file will contain a class that you want to test and -resides in the `lib` folder. The `counter_test.dart` file will contain +The `counter.dart` file contains a class that you want to test and +resides in the `lib` folder. The `counter_test.dart` file contains the tests themselves and lives inside the `test` folder. In general, test files should reside inside a `test` folder located at the root @@ -68,8 +68,8 @@ counter_app/ ## 3. Create a class to test Next, you need a "unit" to test. Remember: "unit" is a fancy name for a -function, method, or class. In this example, create a `Counter` class -inside the `lib/counter.dart` file. It will be responsible for incrementing +function, method, or class. For this example, create a `Counter` class +inside the `lib/counter.dart` file. It is responsible for incrementing and decrementing a `value` starting at `0`. diff --git a/src/docs/cookbook/testing/unit/mocking.md b/src/docs/cookbook/testing/unit/mocking.md index 3c9a0fbdd1c..4ec4f356da6 100644 --- a/src/docs/cookbook/testing/unit/mocking.md +++ b/src/docs/cookbook/testing/unit/mocking.md @@ -9,44 +9,45 @@ next: path: /docs/cookbook/testing/widget/introduction --- -In certain cases, unit tests may depend on classes that fetch data from live +Sometimes, unit tests might depend on classes that fetch data from live web services or databases. This is inconvenient for a few reasons: * Calling live services or databases slows down test execution. - * A passing test may start failing if a web service or database returns + * A passing test might start failing if a web service or database returns unexpected results. This is known as a "flaky test." - * It is difficult to test all possible success & failure scenarios using a - live web service or database. + * It is difficult to test all possible success & failure scenarios + by using a live web service or database. Therefore, rather than relying on a live web service or database, -you can "mock" these dependencies. Mocks allow us to emulate a live -web service or database and return specific results depending on the situation. +you can "mock" these dependencies. Mocks allow emulating a live +web service or database and return specific results depending +on the situation. Generally speaking, you can mock dependencies by creating an alternative -implementation of a class. You can write these alternative implementations by +implementation of a class. Write these alternative implementations by hand or make use of the [Mockito package]({{site.pub-pkg}}/mockito) as a shortcut. This recipe demonstrates the basics of mocking using the Mockito package. -For more information, please see the -[Mockito package documentation]({{site.pub-pkg}}/mockito). +For more information, see the +[Mockito]({{site.pub-pkg}}/mockito) package documentation. ## Directions - 1. Add the `mockito` & `test` dependencies + 1. Add the package dependencies 2. Create a function to test 3. Create a test file with a mock `http.Client` 4. Write a test for each condition 5. Run the tests -## 1. Add the `mockito` dependency +## 1. Add the package dependencies -In order to use the `mockito` package, you first need to add it to the +To use the `mockito` package, add it to the `pubspec.yaml` file along with the `flutter_test` dependency in the `dev_dependencies` section. -You'll also be using the `http` package in this example, and will define that -dependency in the `dependencies` section. +This example also uses the `http` package, +so define that dependency in the `dependencies` section. ```yaml dependencies: @@ -58,17 +59,17 @@ dev_dependencies: ## 2. Create a function to test -In this example, you'll want to unit test the `fetchPost` function from the +In this example, unit test the `fetchPost` function from the [Fetch data from the internet](/docs/cookbook/networking/fetch-data/) recipe. -In order to test this function, you need to make two changes: +To test this function, make two changes: - 1. Provide an `http.Client` to the function. This allows you to provide the + 1. Provide an `http.Client` to the function. This allows providing the correct `http.Client` depending on the situation. - For Flutter and server-side projects, you can provide an `http.IOClient`. - For Browser apps, you can provide an `http.BrowserClient`. - For tests, you provide a mock `http.Client`. + For Flutter and server-side projects, provide an `http.IOClient`. + For Browser apps, provide an `http.BrowserClient`. + For tests, provide a mock `http.Client`. 2. Use the provided `client` to fetch data from the internet, - rather than the static `http.get` method, which is difficult to mock. + rather than the static `http.get()` method, which is difficult to mock. The function should now look like this: @@ -79,7 +80,7 @@ Future fetchPost(http.Client client) async { await client.get('https://jsonplaceholder.typicode.com/posts/1'); if (response.statusCode == 200) { - // If the call to the server was successful, parse the JSON + // If the call to the server was successful, parse the JSON. return Post.fromJson(json.decode(response.body)); } else { // If that call was not successful, throw an error. @@ -97,7 +98,7 @@ create a file called `fetch_post_test.dart` in the root `test` folder. The `MockClient` class implements the `http.Client` class. This allows you to pass the `MockClient` to the `fetchPost` function, -and allow you to return different http responses in each test. +and return different http responses in each test. ```dart @@ -112,13 +113,13 @@ main() { ## 4. Write a test for each condition -If you think about the `fetchPost` function, it will do one of two things: +The `fetchPost()` function does one of two things: - 1. Return a `Post` if the http call succeeds - 2. Throw an `Exception` if the http call fails + 1. Returns a `Post` if the http call succeeds + 2. Throws an `Exception` if the http call fails -Therefore, you'll want to test these two conditions. -You can use the `MockClient` class to return an "Ok" response +Therefore, you want to test these two conditions. +Use the `MockClient` class to return an "Ok" response for the success test, and an error response for the unsuccessful test. To achieve this, use the `when` function provided by Mockito. @@ -158,7 +159,7 @@ main() { ### 5. Run the tests -Now that you have a `fetchPost` function with tests in place, +Now that you have a `fetchPost()` function with tests in place, run the tests. ```terminal diff --git a/src/docs/cookbook/testing/widget/finders.md b/src/docs/cookbook/testing/widget/finders.md index 64266fc4868..142718d544c 100644 --- a/src/docs/cookbook/testing/widget/finders.md +++ b/src/docs/cookbook/testing/widget/finders.md @@ -10,101 +10,105 @@ next: {% assign api = site.api | append: '/flutter' -%} -In order to locate Widgets in a test environment, we need to use `Finder` -classes. While it's possible to write our own `Finder` classes, it's generally -more convenient to locate Widgets using the tools provided by the +To locate widgets in a test environment, use the `Finder` +classes. While it's possible to write your own `Finder` classes, +it's generally more convenient to locate widgets using the tools +provided by the [`flutter_test`]({{api}}/flutter_test/flutter_test-library.html) package. -In this recipe, we'll look at the +This recipe looks at the [`find`]({{api}}/flutter_test/find-constant.html) -constant provided by the `flutter_test` package and demonstrate how to work with -some of the `Finders` it provides. For a full list of available finders, please -consult the +constant provided by the `flutter_test` package, and demonstrates how +to work with some of the `Finders` it provides. For a full list of +available finders, see the [`CommonFinders` documentation]({{api}}/flutter_driver/CommonFinders-class.html). -If you're unfamiliar with Widget testing and the role of `Finder` classes, -review the [Introduction to widget testing](/docs/cookbook/testing/widget/introduction) recipe. +If you're unfamiliar with widget testing and the role of `Finder` classes, +review the [Introduction to widget +testing](/docs/cookbook/testing/widget/introduction) recipe. ### Directions - 1. Find a `Text` Widget - 2. Find a Widget with a specific `Key` - 3. Find a specific Widget instance + 1. Find a `Text` widget + 2. Find a widget with a specific `Key` + 3. Find a specific widget instance -### 1. Find a `Text` Widget +### 1. Find a `Text` widget -In our tests, we often need to find Widgets that contain specific text. This is -exactly what the `find.text` method is for. It will create a `Finder` that -searches for Widgets that display a specific `String` of text. +In testing, you often need to find widgets that contain specific text. +This is exactly what the `find.text()` method is for. It creates a +`Finder` that searches for widgets that display a specific `String` of text. ```dart -testWidgets('finds a Text Widget', (WidgetTester tester) async { - // Build an App with a Text Widget that displays the letter 'H' +testWidgets('finds a Text widget', (WidgetTester tester) async { + // Build an app with a Text widget that displays the letter 'H'. await tester.pumpWidget(MaterialApp( home: Scaffold( body: Text('H'), ), )); - // Find a Widget that displays the letter 'H' + // Find a widget that displays the letter 'H'. expect(find.text('H'), findsOneWidget); }); ``` -### 2. Find a Widget with a specific `Key` +### 2. Find a widget with a specific `Key` -In some cases, we may want to find a Widget based on the Key that has been -provided to it. This can be handy if we're displaying multiple instances of the -same Widget. For example, we might have a `ListView` that displays several -`Text` Widgets that contain the same text. +In some cases, you might want to find a widget based on the Key that has been +provided to it. This can be handy if displaying multiple instances of the +same widget. For example, a `ListView` might display several +`Text` widgets that contain the same text. -In this case, we can provide a `Key` to each Widget in the list. This will allow -us to uniquely identify a specific Widget, making it easier to find the Widget -in the test environment. +In this case, provide a `Key` to each widget in the list. This allows +an app to uniquely identify a specific widget, making it easier to find +the widget in the test environment. ```dart -testWidgets('finds a Widget using a Key', (WidgetTester tester) async { - // Define our test key +testWidgets('finds a widget using a Key', (WidgetTester tester) async { + // Define the test key. final testKey = Key('K'); - // Build a MaterialApp with the testKey + // Build a MaterialApp with the testKey. await tester.pumpWidget(MaterialApp(key: testKey, home: Container())); - // Find the MaterialApp Widget using the testKey + // Find the MaterialApp widget using the testKey. expect(find.byKey(testKey), findsOneWidget); }); ``` -### 3. Find a specific Widget instance +### 3. Find a specific widget instance -Finally, we might be interested in locating a specific instance of a Widget. -For example, this can be useful when creating Widgets that take a `child` -property and we want to ensure we're rendering the `child` Widget. +Finally, you might be interested in locating a specific instance of a widget. +For example, this can be useful when creating widgets that take a `child` +property and you want to ensure you're rendering the `child` widget. ```dart testWidgets('finds a specific instance', (WidgetTester tester) async { final childWidget = Padding(padding: EdgeInsets.zero); - // Provide our childWidget to the Container + // Provide the childWidget to the Container. await tester.pumpWidget(Container(child: childWidget)); - // Search for the childWidget in the tree and verify it exists + // Search for the childWidget in the tree and verify it exists. expect(find.byWidget(childWidget), findsOneWidget); }); ``` ### Summary -The `find` constant provided by the `flutter_test` package gives us several ways -to locate Widgets in the test environment. This recipe demonstrated three of -these methods, and several more methods exist for different purposes. +The `find` constant provided by the `flutter_test` package provides +several ways to locate widgets in the test environment. This recipe +demonstrated three of these methods, and several more methods exist +for different purposes. -If the above examples do not work for a particular use-case, please see the -[`CommonFinders` documentation]({{api}}/flutter_driver/CommonFinders-class.html) +If the above examples do not work for a particular use-case, see the +[`CommonFinders` +documentation]({{api}}/flutter_driver/CommonFinders-class.html) to review all available methods. ### Complete example @@ -114,36 +118,36 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('finds a Text Widget', (WidgetTester tester) async { - // Build an App with a Text Widget that displays the letter 'H' + testWidgets('finds a Text widget', (WidgetTester tester) async { + // Build an App with a Text widget that displays the letter 'H'. await tester.pumpWidget(MaterialApp( home: Scaffold( body: Text('H'), ), )); - // Find a Widget that displays the letter 'H' + // Find a widget that displays the letter 'H'. expect(find.text('H'), findsOneWidget); }); - testWidgets('finds a Widget using a Key', (WidgetTester tester) async { - // Define our test key + testWidgets('finds a widget using a Key', (WidgetTester tester) async { + // Define the test key. final testKey = Key('K'); - // Build a MaterialApp with the testKey + // Build a MaterialApp with the testKey. await tester.pumpWidget(MaterialApp(key: testKey, home: Container())); - // Find the MaterialApp Widget using the testKey + // Find the MaterialApp widget using the testKey. expect(find.byKey(testKey), findsOneWidget); }); testWidgets('finds a specific instance', (WidgetTester tester) async { final childWidget = Padding(padding: EdgeInsets.zero); - // Provide our childWidget to the Container + // Provide the childWidget to the Container. await tester.pumpWidget(Container(child: childWidget)); - // Search for the childWidget in the tree and verify it exists + // Search for the childWidget in the tree and verify it exists. expect(find.byWidget(childWidget), findsOneWidget); }); } diff --git a/src/docs/cookbook/testing/widget/introduction.md b/src/docs/cookbook/testing/widget/introduction.md index b4bd86c302f..df89b61025f 100644 --- a/src/docs/cookbook/testing/widget/introduction.md +++ b/src/docs/cookbook/testing/widget/introduction.md @@ -11,43 +11,46 @@ next: {% assign api = site.api | append: '/flutter' -%} -In the [introduction to unit testing](/docs/cookbook/testing/unit/introduction) recipe, we -learned how to test Dart classes using the `test` package. In order to test -Widget classes, we'll need a few additional tools provided by the +In the [introduction to unit +testing](/docs/cookbook/testing/unit/introduction) recipe, you +learned how to test Dart classes using the `test` package. To test +widget classes, you need a few additional tools provided by the [`flutter_test`]({{api}}/flutter_test/flutter_test-library.html) package, which ships with the Flutter SDK. -The `flutter_test` package provides the following tools for testing Widgets: +The `flutter_test` package provides the following tools for testing widgets: * The [`WidgetTester`]({{api}}/flutter_test/WidgetTester-class.html), - which allows us to build and interact with Widgets in a test environment. - * The [`testWidgets`]({{api}}/flutter_test/testWidgets.html) - function. This function will automatically create a new `WidgetTester` for - each test case, and is used in place of the normal `test` function. - * [`Finder`]({{api}}/flutter_test/Finder-class.html) - classes. These allow us to search for Widgets in the test environment. - * Widget-specific [`Matcher`]({{api}}/package-matcher_matcher/Matcher-class.html) - constants, which help us verify whether a `Finder` locates a Widget or - multiple Widgets in the test environment. - -If this sounds overwhelming, don't worry! We'll see how all of these pieces fit + which allows building and interacting with widgets in a test + environment. + * The [`testWidgets()`]({{api}}/flutter_test/testWidgets.html) + function, which automatically creates a new `WidgetTester` for + each test case, and is used in place of the normal `test()` function. + * The [`Finder`]({{api}}/flutter_test/Finder-class.html) + classes, which allow searching for widgets in the test environment. + * Widget-specific + [`Matcher`]({{api}}/package-matcher_matcher/Matcher-class.html) + constants, which help verify whether a `Finder` locates a widget or + multiple widgets in the test environment. + +If this sounds overwhelming, don't worry. Learn how all of these pieces fit together throughout this recipe. ### Directions 1. Add the `flutter_test` dependency - 2. Create a Widget to test + 2. Create a widget to test 3. Create a `testWidgets` test - 4. Build the Widget using the `WidgetTester` - 5. Search for our Widget using a `Finder` - 6. Verify our Widget is working using a `Matcher` + 4. Build the widget using the `WidgetTester` + 5. Search for the widget using a `Finder` + 6. Verify that the widget uses a `Matcher` ### 1. Add the `flutter_test` dependency -Before we can begin writing tests, we'll need to include the `flutter_test` -dependency in the `dev_dependencies` section of our `pubspec.yaml` file. If -you create a new Flutter project with the command line tools or code editor, -this dependency should already be in place! +Before writing tests, include the `flutter_test` +dependency in the `dev_dependencies` section of the `pubspec.yaml` file. +If creating a new Flutter project with the command line tools or +a code editor, this dependency should already be in place. ```yaml dev_dependencies: @@ -55,10 +58,10 @@ dev_dependencies: sdk: flutter ``` -### 2. Create a Widget to test +### 2. Create a widget to test -Next, we'll need to create a Widget that we can test! For this recipe, we'll -create a Widget that displays a `title` and `message`. +Next, create a widget for testing. For this recipe, +create a widget that displays a `title` and `message`. ```dart class MyWidget extends StatelessWidget { @@ -90,43 +93,43 @@ class MyWidget extends StatelessWidget { ### 3. Create a `testWidgets` test -Now that we have a Widget to test, we can begin writing our first test! To get -started, we'll use the -[`testWidgets`]({{api}}/flutter_test/testWidgets.html) -function provided by the `flutter_test` package to define a test. The -`testWidgets` function will allow us to define a Widget test and will create a -`WidgetTester` for us to work with. +With a widget to test, begin by writing your first test. +Use the +[`testWidgets()`]({{api}}/flutter_test/testWidgets.html) +function provided by the `flutter_test` package to define a test. +The `testWidgets` function allows you to define a widget test and creates a +`WidgetTester` to work with. -Our test will verify that `MyWidget` displays a given title and message. +This test verifies that `MyWidget` displays a given title and message. ```dart void main() { - // Define a test. The TestWidgets function will also provide a WidgetTester - // for us to work with. The WidgetTester will allow us to build and interact - // with Widgets in the test environment. + // Define a test. The TestWidgets function also provides a WidgetTester + // to work with. The WidgetTester allows you to build and interact + // with widgets in the test environment. testWidgets('MyWidget has a title and message', (WidgetTester tester) async { - // Test code will go here! + // Test code goes here. }); } ``` -### 4. Build the Widget using the `WidgetTester` +### 4. Build the widget using the `WidgetTester` -Next, we'll want to build `MyWidget` inside the test environment. To do so, we -can use the -[`pumpWidget`]({{api}}/flutter_test/WidgetTester/pumpWidget.html) -method provided by the `WidgetTester`. The `pumpWidget` method will build and -render the Widget we provide. +Next, build `MyWidget` inside the test environment. To do so, +use the +[`pumpWidget()`]({{api}}/flutter_test/WidgetTester/pumpWidget.html) +method provided by the `WidgetTester`. The `pumpWidget` method builds and +renders the provided widget. -In this case, we'll create a `MyWidget` instance that displays "T" as the title +Create a `MyWidget` instance that displays "T" as the title and "M" as the message. ```dart void main() { testWidgets('MyWidget has a title and message', (WidgetTester tester) async { - // Create the Widget tell the tester to build it + // Create the widget by telling the tester to build it. await tester.pumpWidget(MyWidget(title: 'T', message: 'M')); }); } @@ -134,37 +137,38 @@ void main() { #### Note -After the initial call to `pumpWidget`, the `WidgetTester` provides additional -ways to rebuild the same Widget. This is useful if you're working with a -`StatefulWidget` or animations. +After the initial call to `pumpWidget()`, the `WidgetTester` provides +additional ways to rebuild the same widget. This is useful if you're +working with a `StatefulWidget` or animations. -For example, if we tap a button, and this button calls `setState`, Flutter will -not automatically rebuild your Widget in the test environment. We need to use -one of the following methods to ask Flutter to build our Widget once again. +For example, tapping a button calls `setState()`, but Flutter won't +automatically rebuild your widget in the test environment. +Use one of the following methods to ask Flutter to rebuild the widget. - [tester.pump()]({{api}}/flutter_test/TestWidgetsFlutterBinding/pump.html) - : Triggers a rebuild of the Widget after a given duration. + : Triggers a rebuild of the widget after a given duration. - [tester.pumpAndSettle()]({{api}}/flutter_test/WidgetTester/pumpAndSettle.html) : Repeatedly calls pump with the given duration until there are no longer any frames scheduled. This essentially waits for all animations to complete. -These methods provide fine-grained control over the build lifecycle, which is -particularly useful while testing. +These methods provide fine-grained control over the build lifecycle, +which is particularly useful while testing. -### 5. Search for our Widget using a `Finder` +### 5. Search for our widget using a `Finder` -Now that we've built our Widget in the test environment, we'll want to search -through the Widget tree for the `title` and `message` Text Widgets using a -`Finder`. This will allow us to verify that we're displaying these Widgets -correctly! +With a widget in the test environment, search +through the widget tree for the `title` and `message` +Text widgets using a `Finder`. This allow verifification that +the widgets are displaying correctly. -In this case, we'll use the top-level [`find`]({{api}}/flutter_test/find-constant.html) -method provided by the `flutter_test` package to create our `Finders`. Since we -know we're looking for `Text` widgets, we can use the -[`find.text`]({{api}}/flutter_test/CommonFinders-class.html) +For this purpose, use the top-level +[`find()`]({{api}}/flutter_test/find-constant.html) +method provided by the `flutter_test` package to create the `Finders`. +Since you know you're looking for `Text` widgets, use the +[`find.text()`]({{api}}/flutter_test/CommonFinders-class.html) method. -For more information about `Finder` classes, please see the -[Finding Widgets in a Widget Test](/docs/cookbook/testing/widget/finders) +For more information about `Finder` classes, see the +[Finding widgets in a widget test](/docs/cookbook/testing/widget/finders) recipe. @@ -173,22 +177,23 @@ void main() { testWidgets('MyWidget has a title and message', (WidgetTester tester) async { await tester.pumpWidget(MyWidget(title: 'T', message: 'M')); - // Create our Finders + // Create the Finders. final titleFinder = find.text('T'); final messageFinder = find.text('M'); }); } ``` -### 6. Verify our Widget is working using a `Matcher` +### 6. Verify that the widget uses a `Matcher` -Finally, we can verify the title and message `Text` Widgets appear on screen -using the `Matcher` constants provided by `flutter_test`. `Matcher` classes are -a core part of the `test` package, and provide a common way to verify a given -value meets our expectations. +Finally, verify the title and message `Text` widgets appear on screen +using the `Matcher` constants provided by `flutter_test`. +`Matcher` classes are a core part of the `test` package, +and provide a common way to verify a given +value meets expectations. -In this case, we want to ensure our Widgets appear on screen exactly one time. -Therefore, we can use the +Ensure that the widgets appear on screen exactly one time. +For this purpose, use the [`findsOneWidget`]({{api}}/flutter_test/findsOneWidget-constant.html) `Matcher`. @@ -200,8 +205,8 @@ void main() { final titleFinder = find.text('T'); final messageFinder = find.text('M'); - // Use the `findsOneWidget` matcher provided by flutter_test to verify our - // Text Widgets appear exactly once in the Widget tree + // Use the `findsOneWidget` matcher provided by flutter_test to verify + // that the Text widgets appear exactly once in the widget tree. expect(titleFinder, findsOneWidget); expect(messageFinder, findsOneWidget); }); @@ -210,15 +215,15 @@ void main() { #### Additional Matchers -In addition to `findsOneWidget`, `flutter_test` provides additional matchers for -common cases. +In addition to `findsOneWidget`, `flutter_test` provides additional +matchers for common cases. * [findsNothing]({{api}}/flutter_test/findsNothing-constant.html) - : verifies that no Widgets are found + : verifies that no widgets are found * [findsWidgets]({{api}}/flutter_test/findsWidgets-constant.html) - : verifies one or more Widgets are found + : verifies that one or more widgets are found * [findsNWidgets]({{api}}/flutter_test/findsNWidgets.html) - : verifies a specific number of Widgets are found + : verifies that a specific number of widgets are found ### Complete example @@ -227,19 +232,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - // Define a test. The TestWidgets function will also provide a WidgetTester - // for us to work with. The WidgetTester will allow us to build and interact - // with Widgets in the test environment. + // Define a test. The TestWidgets function also provides a WidgetTester + // to work with. The WidgetTester allows building and interacting + // with widgets in the test environment. testWidgets('MyWidget has a title and message', (WidgetTester tester) async { - // Create the Widget tell the tester to build it + // Create the widget by telling the tester to build it. await tester.pumpWidget(MyWidget(title: 'T', message: 'M')); - // Create our Finders + // Create the Finders. final titleFinder = find.text('T'); final messageFinder = find.text('M'); - // Use the `findsOneWidget` matcher provided by flutter_test to verify our - // Text Widgets appear exactly once in the Widget tree + // Use the `findsOneWidget` matcher provided by flutter_test to + // verify that the Text widgets appear exactly once in the widget tree. expect(titleFinder, findsOneWidget); expect(messageFinder, findsOneWidget); }); diff --git a/src/docs/cookbook/testing/widget/tap-drag.md b/src/docs/cookbook/testing/widget/tap-drag.md index c71f79e0889..2cf00126561 100644 --- a/src/docs/cookbook/testing/widget/tap-drag.md +++ b/src/docs/cookbook/testing/widget/tap-drag.md @@ -7,13 +7,13 @@ prev: {% assign api = site.api | append: '/flutter' -%} -Many of the Widgets we build not only display information, but also respond to -user interaction. This includes buttons that users can tap on, dragging items -across the screen, or entering text into a -[`TextField`]({{api}}/material/TextField-class.html). +Many widgets not only display information, but also respond +to user interaction. This includes buttons that can be tapped, +and [`TextField`]({{api}}/material/TextField-class.html) +for entering text. -In order to test these interactions, we need a way to simulate them in the test -environment. To do so, we can use the +To test these interactions, you need a way to simulate them +in the test environment. For this purpose, use the [`WidgetTester`]({{api}}/flutter_test/WidgetTester-class.html) class provided by the [`flutter_test`]({{api}}/flutter_test/flutter_test-library.html) @@ -25,38 +25,39 @@ The `WidgetTester` provides methods for entering text, tapping, and dragging. * [`tap`]({{api}}/flutter_test/WidgetController/tap.html) * [`drag`]({{api}}/flutter_test/WidgetController/drag.html) -In many cases, user interactions will update the state of our app. In the test -environment, Flutter will not automatically rebuild widgets when the state -changes. To ensure our Widget tree is rebuilt after we simulate a user -interaction, we must call the -[`pump`]({{api}}/flutter_test/WidgetTester/pump.html) or -[`pumpAndSettle`]({{api}}/flutter_test/WidgetTester/pumpAndSettle.html) +In many cases, user interactions update the state of the app. In the test +environment, Flutter doesn't automatically rebuild widgets when the state +changes. To ensure that the widget tree is rebuilt after simulating a user +interaction, call the +[`pump()`]({{api}}/flutter_test/WidgetTester/pump.html) or +[`pumpAndSettle()`]({{api}}/flutter_test/WidgetTester/pumpAndSettle.html) methods provided by the `WidgetTester`. ### Directions - 1. Create a Widget to test + 1. Create a widget to test 2. Enter text in the text field 3. Ensure tapping a button adds the todo 4. Ensure swipe-to-dismiss removes the todo -### 1. Create a Widget to test +### 1. Create a widget to test -For this example, we'll create a basic todo app. It will have three main -features that we'll want to test: +For this example, +create a basic todo app that tests three features: - 1. Enter text into a `TextField` - 2. Tapping a `FloatingActionButton` adds the text to a list of todos - 3. Swipe-to-dismiss removes the item from the list + 1. Entering text into a `TextField` + 2. Tapping a `FloatingActionButton` to add the text to a list of todos + 3. Swiping-to-dismiss toe remove the item from the list -To keep the focus on testing, this recipe will not provide a detailed guide on -how to build the todo app. To learn more about how this app is built, please see -the relevant recipes: +To keep the focus on testing, +this recipe won't provide a detailed guide on how to build the todo app. +To learn more about how this app is built, +see the relevant recipes: - * [Create and style a text field](/docs/cookbook/forms/text-input/) - * [Handling Taps](/docs/cookbook/gestures/handling-taps/) - * [Create a basic list](/docs/cookbook/lists/basic-list/) - * [Implement Swipe to Dismiss](/docs/cookbook/gestures/dismissible/) + * [Create and style a text field](/docs/cookbook/forms/text-input) + * [Handling taps](/docs/cookbook/gestures/handling-taps) + * [Create a basic list](/docs/cookbook/lists/basic-list) + * [Implement swipe to dismiss](/docs/cookbook/gestures/dismissible) ```dart class TodoList extends StatefulWidget { @@ -116,127 +117,127 @@ class _TodoListState extends State { ### 2. Enter text in the text field -Now that we have a todo app, we can begin writing our test! In this case, we'll -start by entering text into the `TextField`. +Now that you have a todo app, begin writing the test. +Start by entering text into the `TextField`. -We can accomplish this task by: +Accomplish this task by: - 1. Building the Widget in the Test Environment + 1. Building the widget in the test environment 2. Using the - [`enterText`]({{api}}/flutter_test/WidgetTester/enterText.html) - method from the `WidgetTester` + [`enterText()`]({{api}}/flutter_test/WidgetTester/enterText.html) + method from the `WidgetTester` ```dart testWidgets('Add and remove a todo', (WidgetTester tester) async { - // Build the Widget + // Build the widget await tester.pumpWidget(TodoList()); - // Enter 'hi' into the TextField + // Enter 'hi' into the TextField. await tester.enterText(find.byType(TextField), 'hi'); }); ``` -**Note:** This recipe builds upon previous Widget testing recipes. To learn the -core concepts of Widget testing, see the following recipes: +{{site.alert.note}} + This recipe builds upon previous widget testing recipes. + To learn the core concepts of widget testing, + see the following recipes: - * [Introduction to Widget testing](.) - * [Finding Widgets in a Widget Test](finders) + * [Introduction to widget testing](.) + * [Finding widgets in a widget test](finders) +{{site.alert.end}} ### 3. Ensure tapping a button adds the todo -After we've entered text into the `TextField`, we'll want to ensure that tapping +After entering text into the `TextField`, ensure that tapping the `FloatingActionButton` adds the item to the list. -This will involve three steps: +This involves three steps: 1. Tap the add button using the - [`tap`]({{api}}/flutter_test/WidgetController/tap.html) + [`tap()`]({{api}}/flutter_test/WidgetController/tap.html) method - 2. Rebuild the Widget after the state has changed using the - [`pump`]({{api}}/flutter_test/TestWidgetsFlutterBinding/pump.html) + 2. Rebuild the widget after the state has changed using the + [`pump()`]({{api}}/flutter_test/TestWidgetsFlutterBinding/pump.html) method - 3. Ensure the list item appears on screen + 3. Ensure that the list item appears on screen ```dart testWidgets('Add and remove a todo', (WidgetTester tester) async { // Enter text code... - // Tap the add button + // Tap the add button. await tester.tap(find.byType(FloatingActionButton)); - // Rebuild the Widget after the state has changed + // Rebuild the widget after the state has changed. await tester.pump(); - // Expect to find the item on screen + // Expect to find the item on screen. expect(find.text('hi'), findsOneWidget); }); ``` ### 4. Ensure swipe-to-dismiss removes the todo -Finally, we can ensure that performing a swipe-to-dismiss action on the todo -item will remove it from the list. This will involve three steps: +Finally, ensure that performing a swipe-to-dismiss action on the todo +item removes it from the list. This involves three steps: 1. Use the - [`drag`]({{api}}/flutter_test/WidgetController/drag.html) - method to perform a swipe-to-dismiss action. + [`drag()`]({{api}}/flutter_test/WidgetController/drag.html) + method to perform a swipe-to-dismiss action. 2. Use the - [`pumpAndSettle`]({{api}}/flutter_test/WidgetTester/pumpAndSettle.html) - method to continually rebuild our Widget tree until the dismiss animation is - complete. - 3. Ensure the item no longer appears on screen. + [`pumpAndSettle()`]({{api}}/flutter_test/WidgetTester/pumpAndSettle.html) + method to continually rebuild the widget tree until the dismiss + animation is complete. + 3. Ensure that the item no longer appears on screen. ```dart testWidgets('Add and remove a todo', (WidgetTester tester) async { // Enter text and add the item... - // Swipe the item to dismiss it + // Swipe the item to dismiss it. await tester.drag(find.byType(Dismissible), Offset(500.0, 0.0)); - // Build the Widget until the dismiss animation ends + // Build the widget until the dismiss animation ends. await tester.pumpAndSettle(); - // Ensure the item is no longer on screen + // Ensure that the item is no longer on screen. expect(find.text('hi'), findsNothing); }); ``` ### Complete example -Once we've completed these steps, we should have a working app with a test to -ensure it works correctly! - ```dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('Add and remove a todo', (WidgetTester tester) async { - // Build the Widget + // Build the widget. await tester.pumpWidget(TodoList()); - // Enter 'hi' into the TextField + // Enter 'hi' into the TextField. await tester.enterText(find.byType(TextField), 'hi'); - // Tap the add button + // Tap the add button. await tester.tap(find.byType(FloatingActionButton)); - // Rebuild the Widget with the new item + // Rebuild the widget with the new item. await tester.pump(); - // Expect to find the item on screen + // Expect to find the item on screen. expect(find.text('hi'), findsOneWidget); - // Swipe the item to dismiss it + // Swipe the item to dismiss it. await tester.drag(find.byType(Dismissible), Offset(500.0, 0.0)); - // Build the Widget until the dismiss animation ends + // Build the widget until the dismiss animation ends. await tester.pumpAndSettle(); - // Ensure the item is no longer on screen + // Ensure that the item is no longer on screen. expect(find.text('hi'), findsNothing); }); } diff --git a/src/docs/deployment/android.md b/src/docs/deployment/android.md index ab53e293668..74a65ad6c13 100644 --- a/src/docs/deployment/android.md +++ b/src/docs/deployment/android.md @@ -151,7 +151,7 @@ Release builds of your app will now be signed automatically. By default, Flutter does not obfuscate or minify the Android host. If you intend to use third-party Java or Android libraries, -you may want to reduce the size of the APK or protect that code from +you might want to reduce the size of the APK or protect that code from reverse engineering. For information on obfuscating Dart code, see [Obfuscating Dart diff --git a/src/docs/deployment/flavors.md b/src/docs/deployment/flavors.md index ad2a99eb94b..5b14eef5250 100644 --- a/src/docs/deployment/flavors.md +++ b/src/docs/deployment/flavors.md @@ -6,9 +6,11 @@ description: How to create build flavors specific to different release types or Do you need to set up product flavors for different development environments or release types? -The community has written some articles you may find useful. +The community has written some articles you might find useful. These articles address flavors for both iOS and Android. -Please see -[Creating flavors of a Flutter app](https://cogitas.net/creating-flavors-of-a-flutter-app/), -[Flavoring Flutter]({{site.medium}}/@salvatoregiordanoo/flavoring-flutter-392aaa875f36) and [Flutter Ready to Go]({{site.medium}}/flutter-community/flutter-ready-to-go-e59873f9d7de). +See [Creating flavors of a Flutter +app](https://cogitas.net/creating-flavors-of-a-flutter-app/), [Flavoring +Flutter]({{site.medium}}/@salvatoregiordanoo/flavoring-flutter-392aaa875f36) +and [Flutter Ready to +Go]({{site.medium}}/flutter-community/flutter-ready-to-go-e59873f9d7de). diff --git a/src/docs/development/data-and-backend/json.md b/src/docs/development/data-and-backend/json.md index dd14ffaa7d9..0aa5a2e4dd4 100644 --- a/src/docs/development/data-and-backend/json.md +++ b/src/docs/development/data-and-backend/json.md @@ -66,7 +66,7 @@ are these kinds of libraries. This approach scales well for a larger project. No hand-written boilerplate is needed, and typos when accessing JSON fields are caught at compile-time. The downside with code generation is that it requires some -initial setup. Also, the generated source files may produce visual clutter +initial setup. Also, the generated source files might produce visual clutter in your project navigator. You might want to use generated code for JSON serialization when you have a diff --git a/src/docs/development/data-and-backend/state-mgmt/declarative.md b/src/docs/development/data-and-backend/state-mgmt/declarative.md index ae6896163c9..c5dfc5ad99c 100644 --- a/src/docs/development/data-and-backend/state-mgmt/declarative.md +++ b/src/docs/development/data-and-backend/state-mgmt/declarative.md @@ -37,5 +37,5 @@ The declarative style of UI programming has many benefits. Remarkably, there is only one code path for any state of the UI. You describe what the UI should look like for any given state, once — and that is it. -At first, this style of programming may not seem as intuitive as the imperative -style. This is why this section is here. Read on. +At first, this style of programming might not seem as intuitive as the +imperative style. This is why this section is here. Read on. diff --git a/src/docs/development/packages-and-plugins/androidx-compatibility.md b/src/docs/development/packages-and-plugins/androidx-compatibility.md index a1ed4db4ad9..5dc736e0e30 100644 --- a/src/docs/development/packages-and-plugins/androidx-compatibility.md +++ b/src/docs/development/packages-and-plugins/androidx-compatibility.md @@ -4,7 +4,7 @@ description: How to fix AndroidX incompatibilities that have been detected by th --- {{site.alert.note}} - You may be directed to this page if the framework detects a problem in your + You might be directed to this page if the framework detects a problem in your Flutter app involving AndroidX incompatibilities. {{site.alert.end}} @@ -176,7 +176,7 @@ packages that are pre AndroidX: - `webview_flutter`: 0.2.0 Note that this is not an exhaustive list of all Flutter plugins -that use AndroidX, and the AndroidX dependency in your app may be +that use AndroidX, and the AndroidX dependency in your app might be coming from another plugin besides these. ## For plugin maintainers: Migrating a Flutter plugin to AndroidX diff --git a/src/docs/development/packages-and-plugins/developing-packages.md b/src/docs/development/packages-and-plugins/developing-packages.md index 1ff7cdd249a..7cc008ad80f 100644 --- a/src/docs/development/packages-and-plugins/developing-packages.md +++ b/src/docs/development/packages-and-plugins/developing-packages.md @@ -27,7 +27,7 @@ minimal package consists of: Packages can contain several kinds of content: * *Dart packages*: General packages written in Dart, for example the - [`path`]({{site.pub}}/packages/path) package. Some of these may + [`path`]({{site.pub}}/packages/path) package. Some of these might contain Flutter specific functionality and thus have a dependency on the Flutter framework, restricting their use to Flutter only, for example the [`fluro`]({{site.pub}}/packages/fluro) package. diff --git a/src/docs/development/packages-and-plugins/using-packages.md b/src/docs/development/packages-and-plugins/using-packages.md index 766c710294a..7b0c849bcf2 100644 --- a/src/docs/development/packages-and-plugins/using-packages.md +++ b/src/docs/development/packages-and-plugins/using-packages.md @@ -4,22 +4,22 @@ description: How to use packages in your Flutter app. --- Flutter supports using shared packages contributed by other developers -to the Flutter and Dart ecosystems. This allows you to quickly build -your app without having to develop everything from scratch. +to the Flutter and Dart ecosystems. This allows quickly building +an app without having to develop everything from scratch. -Existing packages enable many use cases, for example, making network requests +Existing packages enable many use cases for example, making network requests ([`http`](/docs/cookbook/networking/fetch-data)), custom navigation/route handling ([`fluro`]({{site.pub-pkg}}/fluro)), integration with device APIs ([`url_launcher`]({{site.pub-pkg}}/url_launcher) and [`battery`]({{site.pub-pkg}}/battery)), -and using third-party platform SDKs -([Firebase]({{site.github}}/flutter/plugins/blob/master/FlutterFire.md)). +and using third-party platform SDKs like Firebase +([FlutterFire]({{site.github}}/flutter/plugins/blob/master/FlutterFire.md)). -If you are looking to develop a new package, see [developing +To develop a new package, see [developing packages](/docs/development/packages-and-plugins/developing-packages). -If you are looking to add assets, images, or fonts, -whether stored in files or packages, +To add assets, images or fonts, +whether stored in files or packages see [Adding assets and images](/docs/development/ui/assets-and-images). ## Using packages @@ -44,12 +44,12 @@ To add the package, `css_colors`, to an app: and add `css_colors:` under `dependencies`. 1. Install it - * From the terminal: Run `flutter pub get`
+ * From the terminal: Run `flutter pub get`.
**OR** * From Android Studio/IntelliJ: Click **Packages Get** in the action - ribbon at the top of `pubspec.yaml` + ribbon at the top of `pubspec.yaml`. * From VS Code: Click **Get Packages** located in right side of the action - ribbon at the top of `pubspec.yaml` + ribbon at the top of `pubspec.yaml`. 1. Import it * Add a corresponding `import` statement in the Dart code. @@ -58,20 +58,20 @@ To add the package, `css_colors`, to an app: * If the package brings platform-specific code (Java/Kotlin for Android, Swift/Objective-C for iOS), that code must be built into your app. Hot reload and hot restart only update the Dart code, - so a full restart of the app may be required to avoid + so a full restart of the app might be required to avoid errors like `MissingPluginException` when using the package. The [Installing]({{site.pub-pkg}}/css_colors#-installing-tab-) tab available on any package page on Pub is a handy reference for these steps. -For a complete example, see the [CSS Colors example](#css-example) below. +For a complete example, see the [css_colors example](#css-example) below. ### Conflict resolution -Suppose you want to use `some_package` and `another_package` in your app, +Suppose you want to use `some_package` and `another_package` in an app, and both of these depend on `url_launcher`, but in different versions. -Then you have a potential conflict. +That causes a potential conflict. The best way to avoid this is for package authors to use [version ranges]({{site.dart-site}}/tools/pub/dependencies#version-constraints) rather than specific versions when specifying dependencies. @@ -94,7 +94,8 @@ compatible ways. In this situation, the conflict can be resolved by adding a dependency override declaration to the app's `pubspec.yaml` file, forcing the use of a particular version. -To force the use of `url_launcher` version `0.4.3` in the app's `pubspec.yaml`: +To force the use of `url_launcher` version `0.4.3`, +make the following changes to the app's `pubspec.yaml` file: ```yaml dependencies: @@ -108,8 +109,8 @@ If the conflicting dependency is not itself a package, but an Android-specific library like `guava`, the dependency override declaration must be added to Gradle build logic instead. -To force the use of `guava` version `23.0` in the app's -`android/build.gradle` file: +To force the use of `guava` version `23.0`, make the following +changes to the app's `android/build.gradle` file: ```groovy configurations.all { @@ -145,7 +146,7 @@ well as a list of all prior versions ([url_launcher versions]({{site.pub-pkg}}/url_launcher#-versions-tab-)). When a package is added to `pubspec.yaml`, the shorthand form `plugin1:` -means that any version of the plugin1 package may be used. +means that any version of the plugin1 package can be used. To ensure that the app doesn't break when a package is updated, specify a version range using one of the following formats: @@ -158,9 +159,9 @@ specify a version range using one of the following formats: * Range constraints with [*caret syntax*]({{site.dart-site}}/tools/pub/dependencies#caret-syntax) - is similar to regular range constraints: + are similar to regular range constraints: - ``` + ```yaml dependencies: collection: '^0.1.2' ``` @@ -191,7 +192,7 @@ For private plugins, or for packages not ready for publishing, additional dependency options are available: * **Path** dependency: A Flutter app can depend on a plugin via a file system - `path:` dependency. The path can be either relative, or absolute. + `path:` dependency. The path can be either relative or absolute. For example, to depend on a plugin `plugin1` located in a directory next to the app, use the following syntax: @@ -212,8 +213,8 @@ additional dependency options are available: url: git://github.com/flutter/plugin1.git ``` -* **Git** dependency on a package in a folder: By default Pub assumes the - package is located in the root of the Git repository. If that is not the +* **Git** dependency on a package in a folder: Pub assumes the + package is located in the root of the Git repository; if that is not the case, specify the location with the `path` argument. For example: ```yaml @@ -226,22 +227,22 @@ additional dependency options are available: Finally, use the `ref` argument to pin the dependency to a specific git commit, branch, or tag. For more details, see - [Package Dependencies]({{site.dart-site}}/tools/pub/dependencies). + [Package dependencies]({{site.dart-site}}/tools/pub/dependencies). ## Examples -If you are new to using packages, the following examples walk -you through the necessary steps. +The following examples walk through the necessary steps for +using packages. -### Example: Using the CSS Colors package {#css-example} +### Example: Using the css_colors package {#css-example} The [`css_colors`]({{site.pub-pkg}}/css_colors) package -defines color constants for CSS colors, so they can be used +defines color constants for CSS colors, so use the constants wherever the Flutter framework expects the `Color` type. To use this package: -1. Create a new project called 'cssdemo' +1. Create a new project called `cssdemo`. 1. Open `pubspec.yaml`, and add the `css-colors` dependency: @@ -253,7 +254,7 @@ To use this package: ``` 1. Run `flutter pub get` in the terminal, or click **Packages get** in - IntelliJ + IntelliJ. 1. Open `lib/main.dart` and replace its full contents with: @@ -288,14 +289,14 @@ class DemoPage extends StatelessWidget { ### Example: Using the URL Launcher package to launch the browser {#url-example} The [URL Launcher]({{site.pub-pkg}}/url_launcher) plugin -package enables you to open the default browser on the mobile platform to +package enables opening the default browser on the mobile platform to display a given URL, and is supported on both Android and iOS. -It demonstrates how packages may also contain +It demonstrates how packages might also contain platform-specific code—these packages are often called _plugins_. To use this plugin: -1. Create a new project called 'launchdemo' +1. Create a new project called 'launchdemo'. 1. Open `pubspec.yaml`, and add the `url_launcher` dependency: @@ -307,7 +308,7 @@ To use this plugin: ``` 1. Run `flutter pub get` in the terminal, or click **Packages get** in - IntelliJ + IntelliJ. 1. Open `lib/main.dart` and replace its full contents with: diff --git a/src/docs/development/platform-integration/platform-channels.md b/src/docs/development/platform-integration/platform-channels.md index d68e2483437..88c9dbc0193 100644 --- a/src/docs/development/platform-integration/platform-channels.md +++ b/src/docs/development/platform-integration/platform-channels.md @@ -146,7 +146,7 @@ class _MyHomePageState extends State { Next, invoke a method on the method channel, specifying the concrete method to call via the String identifier `getBatteryLevel`. -The call may fail—for example if the platform does not support the +The call might fail—for example if the platform does not support the platform API (such as when running in a simulator), so wrap the `invokeMethod` call in a try-catch statement. diff --git a/src/docs/development/tools/android-studio.md b/src/docs/development/tools/android-studio.md index 47abae65928..6e35330a5e4 100644 --- a/src/docs/development/tools/android-studio.md +++ b/src/docs/development/tools/android-studio.md @@ -240,7 +240,7 @@ installation and getting started instructions, see the [DevTools' docs][]. ## Editing tips for Flutter code -If you have additional tips we should share, please [let us know][]! +If you have additional tips we should share, [let us know][]! ### Assists & Quick Fixes @@ -357,7 +357,7 @@ you can open the Android files as their own project from the start: 2. Open the `android` subdirectory immediately under the flutter app root. For example if the project is called `flutter_app`, open `flutter_app/android`. -If you haven't run your Flutter app yet, you may see Android Studio report a +If you haven't run your Flutter app yet, you might see Android Studio report a build error when you open the `android` project. Run `flutter pub get` in the app's root directory and rebuild the project by selecting **Build > Make** to fix it. @@ -388,7 +388,7 @@ location of the Android SDK: ### Known issues and feedback -Important known issues that may impact your experience are documented +Important known issues that might impact your experience are documented in the [Flutter plugin README][] file. All known bugs are tracked in the issue trackers: diff --git a/src/docs/development/tools/formatting.md b/src/docs/development/tools/formatting.md index 83c44cc74c5..7bd3dec095e 100644 --- a/src/docs/development/tools/formatting.md +++ b/src/docs/development/tools/formatting.md @@ -4,14 +4,14 @@ description: Flutter's code formatter formats your code along recommended style --- -While your code may follow any preferred style—in our -experience—teams of developers may find it more productive to: +While your code might follow any preferred style—in our +experience—teams of developers might find it more productive to: * Have a single, shared style, and * Enforce this style through automatic formatting. -The alternative is often tiring formatting debates during code reviews, where -time may be better spent on code behavior rather than code style. +The alternative is often tiring formatting debates during code reviews, +where time might be better spent on code behavior rather than code style. ## Automatically formatting code in Android Studio and IntelliJ diff --git a/src/docs/development/tools/inspector.md b/src/docs/development/tools/inspector.md index 37aeb64af51..63cbb9b6fab 100644 --- a/src/docs/development/tools/inspector.md +++ b/src/docs/development/tools/inspector.md @@ -37,7 +37,7 @@ on the device and in the widget tree. You can then browse around the interactive widget tree in the IDE to view nearby widgets and see their field values. If you are trying to debug a layout -issue, then the Widget layer’s tree may be insufficiently detailed. In that +issue, then the Widget layer’s tree might be insufficiently detailed. In that case, click the Render Tree tab to view the render tree corresponding to the same location in the tree. When debugging layout issues, the key fields to look at are the `size` and `constraints` fields. The constraints flow down the tree, @@ -50,5 +50,5 @@ For a more complete demonstration of the inspector, see the ## Feedback -If you have suggestions, or encounter issues, please +If you have suggestions, or encounter issues, [file an issue in our tracker]({{site.github}}/flutter/flutter-intellij/issues/new?labels=inspector)! diff --git a/src/docs/development/tools/vs-code.md b/src/docs/development/tools/vs-code.md index ad868b9a92a..64c1c66c873 100644 --- a/src/docs/development/tools/vs-code.md +++ b/src/docs/development/tools/vs-code.md @@ -75,12 +75,14 @@ device name (or the message **No Devices**). ![Flutter device]({% asset tools/vs-code/device_status_bar.png @path %}){:width="450px"} {{site.alert.note}} - - If you do not see a Flutter version number or device info your project may - not have been detected as a Flutter project. Please ensure the folder that - contains your `pubspec.yaml` is inside a VS Code **Workspace Folder** + - If you do not see a Flutter version number or device info your project + might not have been detected as a Flutter project. Ensure the + folder that contains your `pubspec.yaml` is inside a VS Code + **Workspace Folder**. - If the status bar reads **No Devices** Flutter has not been able to discover any connected iOS or Android devices or simulators. - You need to connect a device, or start a simulator, to proceed. + You need to connect a device, or start a simulator or emulator, + to proceed. {{site.alert.end}} The Flutter extension automatically selects the last device connected. @@ -144,15 +146,15 @@ installation and getting started instructions, see the [DevTools' docs][]. ## Editing tips for Flutter code -If you have additional tips we should share, please [let us know][]! +If you have additional tips we should share, [let us know][]! ### Assists & Quick Fixes -Assists are code changes related to a certain code identifier. A number of these -are available when the cursor is placed on a Flutter widget identifier, as -indicated by the yellow lightbulb icon. The assist can be invoked by clicking -the lightbulb, or by using the keyboard shortcut `Ctrl`+`.` (`Cmd`+`.` on Mac), as -illustrated here: +Assists are code changes related to a certain code identifier. +A number of these are available when the cursor is placed on a +Flutter widget identifier, as indicated by the yellow lightbulb icon. +The assist can be invoked by clicking the lightbulb, or by using the +keyboard shortcut `Ctrl`+`.` (`Cmd`+`.` on Mac), as illustrated here: ![Code Assists]({% asset tools/vs-code/assists.png @path %}){:width="467px"} diff --git a/src/docs/development/ui/animations/hero-animations.md b/src/docs/development/ui/animations/hero-animations.md index b9fb3725adb..32827df2785 100644 --- a/src/docs/development/ui/animations/hero-animations.md +++ b/src/docs/development/ui/animations/hero-animations.md @@ -22,7 +22,7 @@ a new screen, containing more details and a "Buy" button. Flying an image from one screen to another is called a _hero animation_ in Flutter, though the same motion is sometimes referred to as a _shared element transition_. -You may want to watch this one-minute video introducing the Hero widget: +You might want to watch this one-minute video introducing the Hero widget: This guide demonstrates how to build standard hero animations, and hero @@ -122,7 +122,7 @@ how Flutter performs a hero animation. diff --git a/src/docs/development/ui/animations/index.md b/src/docs/development/ui/animations/index.md index 67e00c51e59..965ffa0e478 100644 --- a/src/docs/development/ui/animations/index.md +++ b/src/docs/development/ui/animations/index.md @@ -4,21 +4,22 @@ short-title: Animations description: How to perform animations in Flutter --- -Well-designed animations makes a UI feel more intuitive, contribute to the slick -look and feel of a polished app, and improve the user experience. Flutter's -animation support makes it easy to implement a variety of animation types. Many -widgets, especially [Material widgets][], come with the standard motion effects -defined in their design spec, but it's also possible to customize these effects. +Well-designed animations makes a UI feel more intuitive, contribute to +the slick look and feel of a polished app, and improve the user experience. +Flutter's animation support makes it easy to implement a variety of +animation types. Many widgets, especially [Material widgets][], +come with the standard motion effects defined in their design spec, +but it's also possible to customize these effects. -The following resources are a good place to start learning the Flutter animation -framework. Each of these documents shows, step by step, how to write animation -code. +The following resources are a good place to start learning the Flutter +animation framework. Each of these documents shows, step by step, +how to write animation code. {% comment %} More documentation is in the works on how to implement common design patterns, such as shared element transitions, and physics-based animations. -If you have a specific request, please +If you have a specific request, [file an issue]({{site.github}}/flutter/website/issues). {% endcomment -%} @@ -143,8 +144,9 @@ classes. ### Staggered animation -Animations that are broken into smaller motions, where some of the motion is delayed. -The smaller animations may be sequential, or may partially or completely overlap. +Animations that are broken into smaller motions, where some of the motion +is delayed. The smaller animations might be sequential, +or might partially or completely overlap. * [Staggered Animations](/docs/development/ui/animations/staggered-animations) diff --git a/src/docs/development/ui/animations/tutorial.md b/src/docs/development/ui/animations/tutorial.md index a12ec800ca8..02fbb40ab26 100644 --- a/src/docs/development/ui/animations/tutorial.md +++ b/src/docs/development/ui/animations/tutorial.md @@ -63,7 +63,7 @@ animation types is `Animation`. An `Animation` object sequentially generates interpolated numbers between two values over a certain duration. -The output of an `Animation` object may be linear, a curve, a step function, +The output of an `Animation` object might be linear, a curve, a step function, or any other mapping you can devise. Depending on how the `Animation` object is controlled, it could run in reverse, or even switch directions in the middle. @@ -359,7 +359,7 @@ With these few changes, you’ve created your first animation in Flutter!